Repository: adobe/brackets Branch: master Commit: d55ac606ac44 Files: 1740 Total size: 23.1 MB Directory structure: gitextract_0uz8e4xx/ ├── .brackets.json ├── .eslintignore ├── .eslintrc.js ├── .gitattributes ├── .gitignore ├── .gitmodules ├── .npmrc ├── .travis.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Gruntfile.js ├── ISSUE_TEMPLATE.md ├── LICENSE ├── NOTICE ├── README.md ├── build.json ├── npm-shrinkwrap.json ├── package.json ├── samples/ │ ├── bg/ │ │ └── Getting Started/ │ │ ├── index.html │ │ └── main.css │ ├── cs/ │ │ └── Getting Started/ │ │ ├── index.html │ │ └── main.css │ ├── da/ │ │ └── Kom godt i gang/ │ │ ├── index.html │ │ └── main.css │ ├── de/ │ │ └── Erste Schritte/ │ │ ├── index.html │ │ └── main.css │ ├── el/ │ │ └── Getting Started/ │ │ ├── index.html │ │ └── main.css │ ├── es/ │ │ └── Primeros Pasos/ │ │ ├── index.html │ │ └── main.css │ ├── fa-ir/ │ │ └── Getting Started/ │ │ ├── index.html │ │ └── main.css │ ├── fi/ │ │ └── Aloitus/ │ │ ├── index.html │ │ └── main.css │ ├── fr/ │ │ └── Premiers pas/ │ │ ├── index.html │ │ └── main.css │ ├── id/ │ │ └── Memulai/ │ │ ├── index.html │ │ └── main.css │ ├── it/ │ │ └── Primi passi/ │ │ ├── index.html │ │ └── main.css │ ├── ja/ │ │ └── Getting Started/ │ │ ├── index.html │ │ └── main.css │ ├── ko/ │ │ └── Getting Started/ │ │ ├── index.html │ │ └── main.css │ ├── nl/ │ │ └── Aan-de-slag/ │ │ ├── index.html │ │ └── main.css │ ├── pl/ │ │ └── Szybki Start/ │ │ ├── index.html │ │ └── main.css │ ├── pt-br/ │ │ └── Primeiros Passos/ │ │ ├── index.html │ │ └── main.css │ ├── pt-pt/ │ │ └── Primeiros Passos/ │ │ ├── index.html │ │ └── main.css │ ├── root/ │ │ └── Getting Started/ │ │ ├── index.html │ │ └── main.css │ ├── ru/ │ │ └── Getting Started/ │ │ ├── index.html │ │ └── main.css │ ├── sv/ │ │ └── Kom igang/ │ │ ├── index.html │ │ └── main.css │ ├── uk/ │ │ └── Pochynayemo/ │ │ ├── index.html │ │ └── main.css │ ├── zh-cn/ │ │ └── Getting Started/ │ │ ├── index.html │ │ └── main.css │ └── zh-tw/ │ └── Getting Started/ │ ├── index.html │ └── main.css ├── src/ │ ├── JSUtils/ │ │ ├── HintUtils.js │ │ ├── MessageIds.js │ │ ├── Preferences.js │ │ ├── ScopeManager.js │ │ ├── Session.js │ │ ├── node/ │ │ │ ├── ExtractFileContent.js │ │ │ ├── TernNodeDomain.js │ │ │ └── package.json │ │ └── package.json │ ├── LiveDevelopment/ │ │ ├── Agents/ │ │ │ ├── CSSAgent.js │ │ │ ├── ConsoleAgent.js │ │ │ ├── DOMAgent.js │ │ │ ├── DOMHelpers.js │ │ │ ├── DOMNode.js │ │ │ ├── EditAgent.js │ │ │ ├── GotoAgent.js │ │ │ ├── HighlightAgent.js │ │ │ ├── NetworkAgent.js │ │ │ ├── RemoteAgent.js │ │ │ ├── RemoteFunctions.js │ │ │ └── ScriptAgent.js │ │ ├── Documents/ │ │ │ ├── CSSDocument.js │ │ │ ├── CSSPreprocessorDocument.js │ │ │ ├── HTMLDocument.js │ │ │ └── JSDocument.js │ │ ├── Inspector/ │ │ │ ├── Inspector.js │ │ │ ├── Inspector.json │ │ │ ├── inspector.html │ │ │ └── jsdoc.rb │ │ ├── LiveDevMultiBrowser.js │ │ ├── LiveDevServerManager.js │ │ ├── LiveDevelopment.js │ │ ├── LiveDevelopmentUtils.js │ │ ├── MultiBrowserImpl/ │ │ │ ├── README.md │ │ │ ├── documents/ │ │ │ │ ├── LiveCSSDocument.js │ │ │ │ ├── LiveDocument.js │ │ │ │ └── LiveHTMLDocument.js │ │ │ ├── language/ │ │ │ │ ├── HTMLInstrumentation.js │ │ │ │ └── HTMLSimpleDOM.js │ │ │ ├── launchers/ │ │ │ │ ├── Launcher.js │ │ │ │ └── node/ │ │ │ │ └── LauncherDomain.js │ │ │ ├── protocol/ │ │ │ │ ├── LiveDevProtocol.js │ │ │ │ └── remote/ │ │ │ │ ├── DocumentObserver.js │ │ │ │ └── LiveDevProtocolRemote.js │ │ │ └── transports/ │ │ │ ├── NodeSocketTransport.js │ │ │ ├── node/ │ │ │ │ └── NodeSocketTransportDomain.js │ │ │ └── remote/ │ │ │ └── NodeSocketTransportRemote.js │ │ ├── Servers/ │ │ │ ├── BaseServer.js │ │ │ ├── FileServer.js │ │ │ └── UserServer.js │ │ ├── launch.html │ │ ├── main.js │ │ ├── main.less │ │ └── transports/ │ │ ├── WebSocketTransport.js │ │ └── node/ │ │ └── WebSocketTransportDomain.js │ ├── base-config/ │ │ └── keyboard.json │ ├── brackets.config.dev.json │ ├── brackets.config.dist.json │ ├── brackets.config.json │ ├── brackets.config.prerelease.json │ ├── brackets.js │ ├── command/ │ │ ├── CommandManager.js │ │ ├── Commands.js │ │ ├── DefaultMenus.js │ │ ├── KeyBindingManager.js │ │ └── Menus.js │ ├── dependencies.js │ ├── document/ │ │ ├── ChangedDocumentTracker.js │ │ ├── Document.js │ │ ├── DocumentCommandHandlers.js │ │ ├── DocumentManager.js │ │ ├── InMemoryFile.js │ │ └── TextRange.js │ ├── editor/ │ │ ├── CSSInlineEditor.js │ │ ├── CodeHintList.js │ │ ├── CodeHintManager.js │ │ ├── Editor.js │ │ ├── EditorCommandHandlers.js │ │ ├── EditorManager.js │ │ ├── EditorOptionHandlers.js │ │ ├── EditorStatusBar.js │ │ ├── ImageViewer.js │ │ ├── InlineTextEditor.js │ │ ├── InlineWidget.js │ │ └── MultiRangeInlineEditor.js │ ├── extensibility/ │ │ ├── ExtensionManager.js │ │ ├── ExtensionManagerDialog.js │ │ ├── ExtensionManagerView.js │ │ ├── ExtensionManagerViewModel.js │ │ ├── InstallExtensionDialog.js │ │ ├── Package.js │ │ ├── node/ │ │ │ ├── ExtensionManagerDomain.js │ │ │ ├── README.md │ │ │ ├── npm-installer.js │ │ │ ├── package-validator.js │ │ │ └── spec/ │ │ │ ├── Installation.spec.js │ │ │ └── Validation.spec.js │ │ └── registry_utils.js │ ├── extensions/ │ │ ├── default/ │ │ │ ├── AutoUpdate/ │ │ │ │ ├── MessageIds.js │ │ │ │ ├── StateHandler.js │ │ │ │ ├── UpdateInfoBar.js │ │ │ │ ├── UpdateStatus.js │ │ │ │ ├── htmlContent/ │ │ │ │ │ ├── updateBar.html │ │ │ │ │ └── updateStatus.html │ │ │ │ ├── main.js │ │ │ │ ├── node/ │ │ │ │ │ ├── AutoUpdateDomain.js │ │ │ │ │ └── package.json │ │ │ │ └── styles/ │ │ │ │ └── styles.css │ │ │ ├── CSSAtRuleCodeHints/ │ │ │ │ ├── AtRulesDef.json │ │ │ │ ├── main.js │ │ │ │ └── unittests.js │ │ │ ├── CSSCodeHints/ │ │ │ │ ├── CSSProperties.json │ │ │ │ ├── main.js │ │ │ │ ├── styles/ │ │ │ │ │ └── brackets-css-hints.css │ │ │ │ ├── unittest-files/ │ │ │ │ │ ├── region-template.html │ │ │ │ │ └── regions.css │ │ │ │ └── unittests.js │ │ │ ├── CSSPseudoSelectorHints/ │ │ │ │ ├── PseudoSelectors.json │ │ │ │ ├── main.js │ │ │ │ └── unittests.js │ │ │ ├── CloseOthers/ │ │ │ │ ├── main.js │ │ │ │ ├── unittest-files/ │ │ │ │ │ └── dummy.js │ │ │ │ └── unittests.js │ │ │ ├── CodeFolding/ │ │ │ │ ├── Prefs.js │ │ │ │ ├── foldhelpers/ │ │ │ │ │ ├── foldSelected.js │ │ │ │ │ ├── foldcode.js │ │ │ │ │ ├── foldgutter.js │ │ │ │ │ ├── handlebarsFold.js │ │ │ │ │ └── indentFold.js │ │ │ │ ├── main.js │ │ │ │ ├── main.less │ │ │ │ ├── package.json │ │ │ │ ├── unittest-files/ │ │ │ │ │ ├── test.hbs │ │ │ │ │ ├── test.html │ │ │ │ │ └── test.js │ │ │ │ └── unittests.js │ │ │ ├── CommandLineTool/ │ │ │ │ └── main.js │ │ │ ├── DarkTheme/ │ │ │ │ ├── main.less │ │ │ │ └── package.json │ │ │ ├── DebugCommands/ │ │ │ │ ├── ErrorNotification.js │ │ │ │ ├── NodeDebugUtils.js │ │ │ │ ├── htmlContent/ │ │ │ │ │ ├── language-dialog.html │ │ │ │ │ └── perf-dialog.html │ │ │ │ ├── keyboard.json │ │ │ │ ├── main.js │ │ │ │ └── styles.css │ │ │ ├── HTMLCodeHints/ │ │ │ │ ├── HtmlAttributes.json │ │ │ │ ├── HtmlTags.json │ │ │ │ ├── main.js │ │ │ │ └── unittests.js │ │ │ ├── HandlebarsSupport/ │ │ │ │ └── main.js │ │ │ ├── HealthData/ │ │ │ │ ├── HealthDataManager.js │ │ │ │ ├── HealthDataNotification.js │ │ │ │ ├── HealthDataPopup.js │ │ │ │ ├── HealthDataPreview.js │ │ │ │ ├── HealthDataUtils.js │ │ │ │ ├── htmlContent/ │ │ │ │ │ ├── healthdata-popup.html │ │ │ │ │ └── healthdata-preview-dialog.html │ │ │ │ ├── main.js │ │ │ │ ├── styles.css │ │ │ │ ├── thirdparty/ │ │ │ │ │ └── uuid.js │ │ │ │ └── unittests.js │ │ │ ├── HtmlEntityCodeHints/ │ │ │ │ ├── SpecialChars.json │ │ │ │ ├── main.js │ │ │ │ ├── styles.css │ │ │ │ ├── unittest-files/ │ │ │ │ │ └── default.html │ │ │ │ └── unittests.js │ │ │ ├── InAppNotifications/ │ │ │ │ ├── htmlContent/ │ │ │ │ │ └── notificationContainer.html │ │ │ │ ├── main.js │ │ │ │ └── styles/ │ │ │ │ └── styles.css │ │ │ ├── InlineColorEditor/ │ │ │ │ ├── ColorEditor.js │ │ │ │ ├── ColorEditorTemplate.html │ │ │ │ ├── InlineColorEditor.js │ │ │ │ ├── css/ │ │ │ │ │ └── main.less │ │ │ │ ├── main.js │ │ │ │ ├── thirdparty/ │ │ │ │ │ └── tinycolor-min.js │ │ │ │ ├── unittest-files/ │ │ │ │ │ ├── unittests.css │ │ │ │ │ └── unittests.html │ │ │ │ └── unittests.js │ │ │ ├── InlineTimingFunctionEditor/ │ │ │ │ ├── BezierCurveEditor.js │ │ │ │ ├── BezierCurveEditorTemplate.html │ │ │ │ ├── InlineTimingFunctionEditor.js │ │ │ │ ├── Localized.css │ │ │ │ ├── StepEditor.js │ │ │ │ ├── StepEditorTemplate.html │ │ │ │ ├── TimingFunctionUtils.js │ │ │ │ ├── main.js │ │ │ │ ├── main.less │ │ │ │ ├── unittest-files/ │ │ │ │ │ └── unittests.css │ │ │ │ └── unittests.js │ │ │ ├── JSLint/ │ │ │ │ ├── main.js │ │ │ │ ├── unittest-files/ │ │ │ │ │ ├── .brackets.json │ │ │ │ │ ├── different-indent.js │ │ │ │ │ ├── errors.js │ │ │ │ │ └── no-errors.js │ │ │ │ └── unittests.js │ │ │ ├── JavaScriptCodeHints/ │ │ │ │ ├── .gitignore │ │ │ │ ├── HintUtils2.js │ │ │ │ ├── ParameterHintsProvider.js │ │ │ │ ├── fix-acorn.js │ │ │ │ ├── main.js │ │ │ │ ├── npm-shrinkwrap.json │ │ │ │ ├── package.json │ │ │ │ ├── styles/ │ │ │ │ │ └── brackets-js-hints.css │ │ │ │ ├── thirdparty/ │ │ │ │ │ └── requirejs/ │ │ │ │ │ ├── LICENSE │ │ │ │ │ └── require.js │ │ │ │ ├── unittest-files/ │ │ │ │ │ ├── basic-test-files/ │ │ │ │ │ │ ├── MyModule.js │ │ │ │ │ │ ├── file1.js │ │ │ │ │ │ ├── file2.js │ │ │ │ │ │ ├── file3.js │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── test.html │ │ │ │ │ ├── module-test-files/ │ │ │ │ │ │ ├── Car.js │ │ │ │ │ │ ├── china/ │ │ │ │ │ │ │ ├── Cup.js │ │ │ │ │ │ │ └── cupFiller.js │ │ │ │ │ │ ├── credits.js │ │ │ │ │ │ ├── module_tests.js │ │ │ │ │ │ ├── products.js │ │ │ │ │ │ ├── purchase.js │ │ │ │ │ │ └── shirt.js │ │ │ │ │ ├── non-module-test-files/ │ │ │ │ │ │ ├── .jscodehints │ │ │ │ │ │ ├── a/ │ │ │ │ │ │ │ └── a.js │ │ │ │ │ │ ├── app.js │ │ │ │ │ │ ├── b/ │ │ │ │ │ │ │ ├── b.js │ │ │ │ │ │ │ └── b1/ │ │ │ │ │ │ │ └── b1.js │ │ │ │ │ │ ├── c/ │ │ │ │ │ │ │ └── c.js │ │ │ │ │ │ ├── d/ │ │ │ │ │ │ │ ├── d.js │ │ │ │ │ │ │ ├── d2.js │ │ │ │ │ │ │ ├── d23.js │ │ │ │ │ │ │ └── d3-excluded.js │ │ │ │ │ │ └── excluded/ │ │ │ │ │ │ └── e.js │ │ │ │ │ └── preference-test-files/ │ │ │ │ │ ├── defaults-test/ │ │ │ │ │ │ └── .jscodehints │ │ │ │ │ ├── negative-test/ │ │ │ │ │ │ └── .jscodehints │ │ │ │ │ └── positive-test/ │ │ │ │ │ └── .jscodehints │ │ │ │ └── unittests.js │ │ │ ├── JavaScriptQuickEdit/ │ │ │ │ ├── main.js │ │ │ │ ├── unittest-files/ │ │ │ │ │ ├── jquery-ui/ │ │ │ │ │ │ ├── .editorconfig │ │ │ │ │ │ ├── .jshintrc │ │ │ │ │ │ ├── AUTHORS.txt │ │ │ │ │ │ ├── GPL-LICENSE.txt │ │ │ │ │ │ ├── MIT-LICENSE.txt │ │ │ │ │ │ ├── README.md │ │ │ │ │ │ ├── build/ │ │ │ │ │ │ │ ├── release/ │ │ │ │ │ │ │ │ ├── changelog-shell │ │ │ │ │ │ │ │ └── prepare-release │ │ │ │ │ │ │ ├── tasks/ │ │ │ │ │ │ │ │ ├── build.js │ │ │ │ │ │ │ │ └── testswarm.js │ │ │ │ │ │ │ └── themes │ │ │ │ │ │ ├── demos/ │ │ │ │ │ │ │ ├── accordion/ │ │ │ │ │ │ │ │ ├── collapsible.html │ │ │ │ │ │ │ │ ├── custom-icons.html │ │ │ │ │ │ │ │ ├── default.html │ │ │ │ │ │ │ │ ├── fillspace.html │ │ │ │ │ │ │ │ ├── hoverintent.html │ │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ │ ├── no-auto-height.html │ │ │ │ │ │ │ │ └── sortable.html │ │ │ │ │ │ │ ├── addClass/ │ │ │ │ │ │ │ │ ├── default.html │ │ │ │ │ │ │ │ └── index.html │ │ │ │ │ │ │ ├── animate/ │ │ │ │ │ │ │ │ ├── default.html │ │ │ │ │ │ │ │ └── index.html │ │ │ │ │ │ │ ├── autocomplete/ │ │ │ │ │ │ │ │ ├── categories.html │ │ │ │ │ │ │ │ ├── combobox.html │ │ │ │ │ │ │ │ ├── custom-data.html │ │ │ │ │ │ │ │ ├── default.html │ │ │ │ │ │ │ │ ├── folding.html │ │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ │ ├── london.xml │ │ │ │ │ │ │ │ ├── maxheight.html │ │ │ │ │ │ │ │ ├── multiple-remote.html │ │ │ │ │ │ │ │ ├── multiple.html │ │ │ │ │ │ │ │ ├── remote-jsonp.html │ │ │ │ │ │ │ │ ├── remote-with-cache.html │ │ │ │ │ │ │ │ ├── remote.html │ │ │ │ │ │ │ │ ├── search.php │ │ │ │ │ │ │ │ └── xml.html │ │ │ │ │ │ │ ├── button/ │ │ │ │ │ │ │ │ ├── checkbox.html │ │ │ │ │ │ │ │ ├── default.html │ │ │ │ │ │ │ │ ├── icons.html │ │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ │ ├── radio.html │ │ │ │ │ │ │ │ ├── splitbutton.html │ │ │ │ │ │ │ │ └── toolbar.html │ │ │ │ │ │ │ ├── datepicker/ │ │ │ │ │ │ │ │ ├── alt-field.html │ │ │ │ │ │ │ │ ├── animation.html │ │ │ │ │ │ │ │ ├── buttonbar.html │ │ │ │ │ │ │ │ ├── date-formats.html │ │ │ │ │ │ │ │ ├── date-range.html │ │ │ │ │ │ │ │ ├── default.html │ │ │ │ │ │ │ │ ├── dropdown-month-year.html │ │ │ │ │ │ │ │ ├── icon-trigger.html │ │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ │ ├── inline.html │ │ │ │ │ │ │ │ ├── localization.html │ │ │ │ │ │ │ │ ├── min-max.html │ │ │ │ │ │ │ │ ├── multiple-calendars.html │ │ │ │ │ │ │ │ ├── other-months.html │ │ │ │ │ │ │ │ └── show-week.html │ │ │ │ │ │ │ ├── demos.css │ │ │ │ │ │ │ ├── dialog/ │ │ │ │ │ │ │ │ ├── animated.html │ │ │ │ │ │ │ │ ├── default.html │ │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ │ ├── modal-confirmation.html │ │ │ │ │ │ │ │ ├── modal-form.html │ │ │ │ │ │ │ │ ├── modal-message.html │ │ │ │ │ │ │ │ └── modal.html │ │ │ │ │ │ │ ├── draggable/ │ │ │ │ │ │ │ │ ├── constrain-movement.html │ │ │ │ │ │ │ │ ├── cursor-style.html │ │ │ │ │ │ │ │ ├── default.html │ │ │ │ │ │ │ │ ├── delay-start.html │ │ │ │ │ │ │ │ ├── events.html │ │ │ │ │ │ │ │ ├── handle.html │ │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ │ ├── revert.html │ │ │ │ │ │ │ │ ├── scroll.html │ │ │ │ │ │ │ │ ├── snap-to.html │ │ │ │ │ │ │ │ ├── sortable.html │ │ │ │ │ │ │ │ └── visual-feedback.html │ │ │ │ │ │ │ ├── droppable/ │ │ │ │ │ │ │ │ ├── accepted-elements.html │ │ │ │ │ │ │ │ ├── default.html │ │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ │ ├── photo-manager.html │ │ │ │ │ │ │ │ ├── propagation.html │ │ │ │ │ │ │ │ ├── revert.html │ │ │ │ │ │ │ │ ├── shopping-cart.html │ │ │ │ │ │ │ │ └── visual-feedback.html │ │ │ │ │ │ │ ├── effect/ │ │ │ │ │ │ │ │ ├── default.html │ │ │ │ │ │ │ │ ├── easing.html │ │ │ │ │ │ │ │ └── index.html │ │ │ │ │ │ │ ├── hide/ │ │ │ │ │ │ │ │ ├── default.html │ │ │ │ │ │ │ │ └── index.html │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ ├── menu/ │ │ │ │ │ │ │ │ ├── default.html │ │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ │ ├── navigationmenu.html │ │ │ │ │ │ │ │ └── topalignmenu.html │ │ │ │ │ │ │ ├── position/ │ │ │ │ │ │ │ │ ├── cycler.html │ │ │ │ │ │ │ │ ├── default.html │ │ │ │ │ │ │ │ └── index.html │ │ │ │ │ │ │ ├── progressbar/ │ │ │ │ │ │ │ │ ├── animated.html │ │ │ │ │ │ │ │ ├── default.html │ │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ │ └── resize.html │ │ │ │ │ │ │ ├── removeClass/ │ │ │ │ │ │ │ │ ├── default.html │ │ │ │ │ │ │ │ └── index.html │ │ │ │ │ │ │ ├── resizable/ │ │ │ │ │ │ │ │ ├── animate.html │ │ │ │ │ │ │ │ ├── aspect-ratio.html │ │ │ │ │ │ │ │ ├── constrain-area.html │ │ │ │ │ │ │ │ ├── default.html │ │ │ │ │ │ │ │ ├── delay-start.html │ │ │ │ │ │ │ │ ├── helper.html │ │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ │ ├── max-min.html │ │ │ │ │ │ │ │ ├── snap-to-grid.html │ │ │ │ │ │ │ │ ├── synchronous-resize.html │ │ │ │ │ │ │ │ ├── textarea.html │ │ │ │ │ │ │ │ └── visual-feedback.html │ │ │ │ │ │ │ ├── selectable/ │ │ │ │ │ │ │ │ ├── default.html │ │ │ │ │ │ │ │ ├── display-grid.html │ │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ │ └── serialize.html │ │ │ │ │ │ │ ├── show/ │ │ │ │ │ │ │ │ ├── default.html │ │ │ │ │ │ │ │ └── index.html │ │ │ │ │ │ │ ├── slider/ │ │ │ │ │ │ │ │ ├── colorpicker.html │ │ │ │ │ │ │ │ ├── default.html │ │ │ │ │ │ │ │ ├── hotelrooms.html │ │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ │ ├── multiple-vertical.html │ │ │ │ │ │ │ │ ├── range-vertical.html │ │ │ │ │ │ │ │ ├── range.html │ │ │ │ │ │ │ │ ├── rangemax.html │ │ │ │ │ │ │ │ ├── rangemin.html │ │ │ │ │ │ │ │ ├── side-scroll.html │ │ │ │ │ │ │ │ ├── slider-vertical.html │ │ │ │ │ │ │ │ ├── steps.html │ │ │ │ │ │ │ │ └── tabs.html │ │ │ │ │ │ │ ├── sortable/ │ │ │ │ │ │ │ │ ├── connect-lists-through-tabs.html │ │ │ │ │ │ │ │ ├── connect-lists.html │ │ │ │ │ │ │ │ ├── default.html │ │ │ │ │ │ │ │ ├── delay-start.html │ │ │ │ │ │ │ │ ├── display-grid.html │ │ │ │ │ │ │ │ ├── empty-lists.html │ │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ │ ├── items.html │ │ │ │ │ │ │ │ ├── placeholder.html │ │ │ │ │ │ │ │ └── portlets.html │ │ │ │ │ │ │ ├── spinner/ │ │ │ │ │ │ │ │ ├── currency.html │ │ │ │ │ │ │ │ ├── decimal.html │ │ │ │ │ │ │ │ ├── default.html │ │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ │ ├── latlong.html │ │ │ │ │ │ │ │ ├── overflow.html │ │ │ │ │ │ │ │ └── time.html │ │ │ │ │ │ │ ├── switchClass/ │ │ │ │ │ │ │ │ ├── default.html │ │ │ │ │ │ │ │ └── index.html │ │ │ │ │ │ │ ├── tabs/ │ │ │ │ │ │ │ │ ├── ajax/ │ │ │ │ │ │ │ │ │ ├── content1.html │ │ │ │ │ │ │ │ │ ├── content2.html │ │ │ │ │ │ │ │ │ ├── content3-slow.php │ │ │ │ │ │ │ │ │ └── content4-broken.php │ │ │ │ │ │ │ │ ├── ajax.html │ │ │ │ │ │ │ │ ├── bottom.html │ │ │ │ │ │ │ │ ├── collapsible.html │ │ │ │ │ │ │ │ ├── default.html │ │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ │ ├── manipulation.html │ │ │ │ │ │ │ │ ├── mouseover.html │ │ │ │ │ │ │ │ ├── sortable.html │ │ │ │ │ │ │ │ └── vertical.html │ │ │ │ │ │ │ ├── toggle/ │ │ │ │ │ │ │ │ ├── default.html │ │ │ │ │ │ │ │ └── index.html │ │ │ │ │ │ │ ├── toggleClass/ │ │ │ │ │ │ │ │ ├── default.html │ │ │ │ │ │ │ │ └── index.html │ │ │ │ │ │ │ ├── tooltip/ │ │ │ │ │ │ │ │ ├── ajax/ │ │ │ │ │ │ │ │ │ ├── content1.html │ │ │ │ │ │ │ │ │ └── content2.html │ │ │ │ │ │ │ │ ├── custom-animation.html │ │ │ │ │ │ │ │ ├── custom-content.html │ │ │ │ │ │ │ │ ├── custom-style.html │ │ │ │ │ │ │ │ ├── default.html │ │ │ │ │ │ │ │ ├── forms.html │ │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ │ ├── tracking.html │ │ │ │ │ │ │ │ └── video-player.html │ │ │ │ │ │ │ └── widget/ │ │ │ │ │ │ │ ├── default.html │ │ │ │ │ │ │ └── index.html │ │ │ │ │ │ ├── external/ │ │ │ │ │ │ │ ├── globalize.culture.de-DE.js │ │ │ │ │ │ │ ├── globalize.culture.ja-JP.js │ │ │ │ │ │ │ ├── globalize.js │ │ │ │ │ │ │ ├── jquery.bgiframe-2.1.2.js │ │ │ │ │ │ │ ├── jquery.cookie.js │ │ │ │ │ │ │ ├── jquery.metadata.js │ │ │ │ │ │ │ ├── jquery.mousewheel-3.0.4.js │ │ │ │ │ │ │ ├── jshint.js │ │ │ │ │ │ │ ├── qunit.css │ │ │ │ │ │ │ └── qunit.js │ │ │ │ │ │ ├── grunt.js │ │ │ │ │ │ ├── jquery-1.7.2.js │ │ │ │ │ │ ├── package.json │ │ │ │ │ │ ├── tests/ │ │ │ │ │ │ │ ├── .jshintrc │ │ │ │ │ │ │ ├── index.css │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ ├── index.js │ │ │ │ │ │ │ ├── jquery-1.6.1.js │ │ │ │ │ │ │ ├── jquery-1.6.2.js │ │ │ │ │ │ │ ├── jquery-1.6.3.js │ │ │ │ │ │ │ ├── jquery-1.6.4.js │ │ │ │ │ │ │ ├── jquery-1.6.js │ │ │ │ │ │ │ ├── jquery-1.7.1.js │ │ │ │ │ │ │ ├── jquery-1.7.2.js │ │ │ │ │ │ │ ├── jquery-1.7.js │ │ │ │ │ │ │ ├── jquery.js │ │ │ │ │ │ │ ├── jquery.simulate.js │ │ │ │ │ │ │ ├── unit/ │ │ │ │ │ │ │ │ ├── accordion/ │ │ │ │ │ │ │ │ │ ├── accordion.html │ │ │ │ │ │ │ │ │ ├── accordion_common.js │ │ │ │ │ │ │ │ │ ├── accordion_common_deprecated.js │ │ │ │ │ │ │ │ │ ├── accordion_core.js │ │ │ │ │ │ │ │ │ ├── accordion_deprecated.html │ │ │ │ │ │ │ │ │ ├── accordion_deprecated.js │ │ │ │ │ │ │ │ │ ├── accordion_events.js │ │ │ │ │ │ │ │ │ ├── accordion_methods.js │ │ │ │ │ │ │ │ │ ├── accordion_options.js │ │ │ │ │ │ │ │ │ ├── accordion_test_helpers.js │ │ │ │ │ │ │ │ │ └── all.html │ │ │ │ │ │ │ │ ├── all-active.html │ │ │ │ │ │ │ │ ├── all.html │ │ │ │ │ │ │ │ ├── autocomplete/ │ │ │ │ │ │ │ │ │ ├── all.html │ │ │ │ │ │ │ │ │ ├── autocomplete.html │ │ │ │ │ │ │ │ │ ├── autocomplete_common.js │ │ │ │ │ │ │ │ │ ├── autocomplete_core.js │ │ │ │ │ │ │ │ │ ├── autocomplete_events.js │ │ │ │ │ │ │ │ │ ├── autocomplete_methods.js │ │ │ │ │ │ │ │ │ ├── autocomplete_options.js │ │ │ │ │ │ │ │ │ ├── remote_object_array_labels.txt │ │ │ │ │ │ │ │ │ ├── remote_object_array_values.txt │ │ │ │ │ │ │ │ │ └── remote_string_array.txt │ │ │ │ │ │ │ │ ├── button/ │ │ │ │ │ │ │ │ │ ├── all.html │ │ │ │ │ │ │ │ │ ├── button.html │ │ │ │ │ │ │ │ │ ├── button_common.js │ │ │ │ │ │ │ │ │ ├── button_core.js │ │ │ │ │ │ │ │ │ ├── button_events.js │ │ │ │ │ │ │ │ │ ├── button_methods.js │ │ │ │ │ │ │ │ │ ├── button_options.js │ │ │ │ │ │ │ │ │ └── button_tickets.js │ │ │ │ │ │ │ │ ├── core/ │ │ │ │ │ │ │ │ │ ├── all.html │ │ │ │ │ │ │ │ │ ├── core.html │ │ │ │ │ │ │ │ │ ├── core.js │ │ │ │ │ │ │ │ │ └── selector.js │ │ │ │ │ │ │ │ ├── datepicker/ │ │ │ │ │ │ │ │ │ ├── all.html │ │ │ │ │ │ │ │ │ ├── datepicker.html │ │ │ │ │ │ │ │ │ ├── datepicker_core.js │ │ │ │ │ │ │ │ │ ├── datepicker_defaults.js │ │ │ │ │ │ │ │ │ ├── datepicker_events.js │ │ │ │ │ │ │ │ │ ├── datepicker_methods.js │ │ │ │ │ │ │ │ │ ├── datepicker_options.js │ │ │ │ │ │ │ │ │ └── datepicker_tickets.js │ │ │ │ │ │ │ │ ├── dialog/ │ │ │ │ │ │ │ │ │ ├── all.html │ │ │ │ │ │ │ │ │ ├── dialog.html │ │ │ │ │ │ │ │ │ ├── dialog_common.js │ │ │ │ │ │ │ │ │ ├── dialog_core.js │ │ │ │ │ │ │ │ │ ├── dialog_events.js │ │ │ │ │ │ │ │ │ ├── dialog_methods.js │ │ │ │ │ │ │ │ │ ├── dialog_options.js │ │ │ │ │ │ │ │ │ └── dialog_tickets.js │ │ │ │ │ │ │ │ ├── draggable/ │ │ │ │ │ │ │ │ │ ├── all.html │ │ │ │ │ │ │ │ │ ├── draggable.html │ │ │ │ │ │ │ │ │ ├── draggable_common.js │ │ │ │ │ │ │ │ │ ├── draggable_core.js │ │ │ │ │ │ │ │ │ ├── draggable_events.js │ │ │ │ │ │ │ │ │ ├── draggable_methods.js │ │ │ │ │ │ │ │ │ └── draggable_options.js │ │ │ │ │ │ │ │ ├── droppable/ │ │ │ │ │ │ │ │ │ ├── all.html │ │ │ │ │ │ │ │ │ ├── droppable.html │ │ │ │ │ │ │ │ │ ├── droppable_common.js │ │ │ │ │ │ │ │ │ ├── droppable_core.js │ │ │ │ │ │ │ │ │ ├── droppable_events.js │ │ │ │ │ │ │ │ │ ├── droppable_methods.js │ │ │ │ │ │ │ │ │ └── droppable_options.js │ │ │ │ │ │ │ │ ├── effects/ │ │ │ │ │ │ │ │ │ ├── all.html │ │ │ │ │ │ │ │ │ ├── effects.html │ │ │ │ │ │ │ │ │ ├── effects_core.js │ │ │ │ │ │ │ │ │ └── effects_scale.js │ │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ │ ├── menu/ │ │ │ │ │ │ │ │ │ ├── all.html │ │ │ │ │ │ │ │ │ ├── menu.html │ │ │ │ │ │ │ │ │ ├── menu_common.js │ │ │ │ │ │ │ │ │ ├── menu_core.js │ │ │ │ │ │ │ │ │ ├── menu_events.js │ │ │ │ │ │ │ │ │ ├── menu_methods.js │ │ │ │ │ │ │ │ │ ├── menu_options.js │ │ │ │ │ │ │ │ │ └── menu_test_helpers.js │ │ │ │ │ │ │ │ ├── position/ │ │ │ │ │ │ │ │ │ ├── all.html │ │ │ │ │ │ │ │ │ ├── position.html │ │ │ │ │ │ │ │ │ ├── position_core.js │ │ │ │ │ │ │ │ │ ├── position_deprecated.html │ │ │ │ │ │ │ │ │ └── position_deprecated.js │ │ │ │ │ │ │ │ ├── progressbar/ │ │ │ │ │ │ │ │ │ ├── all.html │ │ │ │ │ │ │ │ │ ├── progressbar.html │ │ │ │ │ │ │ │ │ ├── progressbar_common.js │ │ │ │ │ │ │ │ │ ├── progressbar_core.js │ │ │ │ │ │ │ │ │ ├── progressbar_events.js │ │ │ │ │ │ │ │ │ ├── progressbar_methods.js │ │ │ │ │ │ │ │ │ └── progressbar_options.js │ │ │ │ │ │ │ │ ├── qunit-composite.css │ │ │ │ │ │ │ │ ├── qunit-composite.js │ │ │ │ │ │ │ │ ├── resizable/ │ │ │ │ │ │ │ │ │ ├── all.html │ │ │ │ │ │ │ │ │ ├── resizable.html │ │ │ │ │ │ │ │ │ ├── resizable_common.js │ │ │ │ │ │ │ │ │ ├── resizable_core.js │ │ │ │ │ │ │ │ │ ├── resizable_events.js │ │ │ │ │ │ │ │ │ ├── resizable_methods.js │ │ │ │ │ │ │ │ │ └── resizable_options.js │ │ │ │ │ │ │ │ ├── selectable/ │ │ │ │ │ │ │ │ │ ├── all.html │ │ │ │ │ │ │ │ │ ├── selectable.html │ │ │ │ │ │ │ │ │ ├── selectable_common.js │ │ │ │ │ │ │ │ │ ├── selectable_core.js │ │ │ │ │ │ │ │ │ ├── selectable_events.js │ │ │ │ │ │ │ │ │ ├── selectable_methods.js │ │ │ │ │ │ │ │ │ └── selectable_options.js │ │ │ │ │ │ │ │ ├── slider/ │ │ │ │ │ │ │ │ │ ├── all.html │ │ │ │ │ │ │ │ │ ├── slider.html │ │ │ │ │ │ │ │ │ ├── slider_common.js │ │ │ │ │ │ │ │ │ ├── slider_core.js │ │ │ │ │ │ │ │ │ ├── slider_events.js │ │ │ │ │ │ │ │ │ ├── slider_methods.js │ │ │ │ │ │ │ │ │ └── slider_options.js │ │ │ │ │ │ │ │ ├── sortable/ │ │ │ │ │ │ │ │ │ ├── all.html │ │ │ │ │ │ │ │ │ ├── sortable.html │ │ │ │ │ │ │ │ │ ├── sortable_common.js │ │ │ │ │ │ │ │ │ ├── sortable_core.js │ │ │ │ │ │ │ │ │ ├── sortable_events.js │ │ │ │ │ │ │ │ │ ├── sortable_methods.js │ │ │ │ │ │ │ │ │ ├── sortable_options.js │ │ │ │ │ │ │ │ │ └── sortable_tickets.js │ │ │ │ │ │ │ │ ├── spinner/ │ │ │ │ │ │ │ │ │ ├── all.html │ │ │ │ │ │ │ │ │ ├── spinner.html │ │ │ │ │ │ │ │ │ ├── spinner_common.js │ │ │ │ │ │ │ │ │ ├── spinner_core.js │ │ │ │ │ │ │ │ │ ├── spinner_events.js │ │ │ │ │ │ │ │ │ ├── spinner_methods.js │ │ │ │ │ │ │ │ │ ├── spinner_options.js │ │ │ │ │ │ │ │ │ └── spinner_test_helpers.js │ │ │ │ │ │ │ │ ├── subsuite.js │ │ │ │ │ │ │ │ ├── swarminject.js │ │ │ │ │ │ │ │ ├── tabs/ │ │ │ │ │ │ │ │ │ ├── all.html │ │ │ │ │ │ │ │ │ ├── data/ │ │ │ │ │ │ │ │ │ │ └── test.html │ │ │ │ │ │ │ │ │ ├── tabs.html │ │ │ │ │ │ │ │ │ ├── tabs_common.js │ │ │ │ │ │ │ │ │ ├── tabs_common_deprecated.js │ │ │ │ │ │ │ │ │ ├── tabs_core.js │ │ │ │ │ │ │ │ │ ├── tabs_deprecated.html │ │ │ │ │ │ │ │ │ ├── tabs_deprecated.js │ │ │ │ │ │ │ │ │ ├── tabs_events.js │ │ │ │ │ │ │ │ │ ├── tabs_methods.js │ │ │ │ │ │ │ │ │ ├── tabs_options.js │ │ │ │ │ │ │ │ │ └── tabs_test_helpers.js │ │ │ │ │ │ │ │ ├── testsuite.js │ │ │ │ │ │ │ │ ├── tooltip/ │ │ │ │ │ │ │ │ │ ├── all.html │ │ │ │ │ │ │ │ │ ├── tooltip.html │ │ │ │ │ │ │ │ │ ├── tooltip_common.js │ │ │ │ │ │ │ │ │ ├── tooltip_core.js │ │ │ │ │ │ │ │ │ ├── tooltip_events.js │ │ │ │ │ │ │ │ │ ├── tooltip_methods.js │ │ │ │ │ │ │ │ │ └── tooltip_options.js │ │ │ │ │ │ │ │ └── widget/ │ │ │ │ │ │ │ │ ├── all.html │ │ │ │ │ │ │ │ ├── widget.html │ │ │ │ │ │ │ │ ├── widget_animation.js │ │ │ │ │ │ │ │ ├── widget_core.js │ │ │ │ │ │ │ │ └── widget_extend.js │ │ │ │ │ │ │ └── visual/ │ │ │ │ │ │ │ ├── accordion/ │ │ │ │ │ │ │ │ └── icons.html │ │ │ │ │ │ │ ├── addClass/ │ │ │ │ │ │ │ │ └── queue.html │ │ │ │ │ │ │ ├── button/ │ │ │ │ │ │ │ │ ├── button.html │ │ │ │ │ │ │ │ └── performance.html │ │ │ │ │ │ │ ├── compound/ │ │ │ │ │ │ │ │ ├── accordion_tabs.html │ │ │ │ │ │ │ │ ├── datepicker_dialog.html │ │ │ │ │ │ │ │ ├── dialog_widgets.html │ │ │ │ │ │ │ │ ├── draggable_accordion.html │ │ │ │ │ │ │ │ ├── draggable_accordion_accordion_tabs_draggable.html │ │ │ │ │ │ │ │ ├── sortable_accordion_sortable_tabs.html │ │ │ │ │ │ │ │ ├── tabs_tabs.html │ │ │ │ │ │ │ │ └── tabs_tooltips.html │ │ │ │ │ │ │ ├── dialog/ │ │ │ │ │ │ │ │ └── performance.html │ │ │ │ │ │ │ ├── effects/ │ │ │ │ │ │ │ │ ├── all.html │ │ │ │ │ │ │ │ ├── effects.css │ │ │ │ │ │ │ │ ├── effects.js │ │ │ │ │ │ │ │ └── scale.html │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ ├── menu/ │ │ │ │ │ │ │ │ └── menu.html │ │ │ │ │ │ │ ├── position/ │ │ │ │ │ │ │ │ ├── position.html │ │ │ │ │ │ │ │ └── position_feedback.html │ │ │ │ │ │ │ ├── theme.html │ │ │ │ │ │ │ ├── tooltip/ │ │ │ │ │ │ │ │ ├── animations.html │ │ │ │ │ │ │ │ └── tooltip.html │ │ │ │ │ │ │ └── visual.css │ │ │ │ │ │ ├── themes/ │ │ │ │ │ │ │ └── base/ │ │ │ │ │ │ │ ├── jquery.ui.accordion.css │ │ │ │ │ │ │ ├── jquery.ui.all.css │ │ │ │ │ │ │ ├── jquery.ui.autocomplete.css │ │ │ │ │ │ │ ├── jquery.ui.base.css │ │ │ │ │ │ │ ├── jquery.ui.button.css │ │ │ │ │ │ │ ├── jquery.ui.core.css │ │ │ │ │ │ │ ├── jquery.ui.datepicker.css │ │ │ │ │ │ │ ├── jquery.ui.dialog.css │ │ │ │ │ │ │ ├── jquery.ui.menu.css │ │ │ │ │ │ │ ├── jquery.ui.progressbar.css │ │ │ │ │ │ │ ├── jquery.ui.resizable.css │ │ │ │ │ │ │ ├── jquery.ui.selectable.css │ │ │ │ │ │ │ ├── jquery.ui.slider.css │ │ │ │ │ │ │ ├── jquery.ui.spinner.css │ │ │ │ │ │ │ ├── jquery.ui.tabs.css │ │ │ │ │ │ │ ├── jquery.ui.theme.css │ │ │ │ │ │ │ └── jquery.ui.tooltip.css │ │ │ │ │ │ └── ui/ │ │ │ │ │ │ ├── .jshintrc │ │ │ │ │ │ ├── i18n/ │ │ │ │ │ │ │ ├── jquery.ui.datepicker-af.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-ar-DZ.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-ar.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-az.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-bg.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-bs.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-ca.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-cs.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-cy-GB.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-da.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-de.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-el.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-en-AU.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-en-GB.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-en-NZ.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-eo.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-es.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-et.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-eu.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-fa.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-fi.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-fo.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-fr-CH.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-fr.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-gl.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-he.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-hi.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-hr.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-hu.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-hy.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-id.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-is.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-it.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-ja.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-ka.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-kk.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-km.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-ko.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-lb.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-lt.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-lv.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-mk.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-ml.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-ms.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-nl-BE.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-nl.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-no.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-pl.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-pt-BR.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-pt.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-rm.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-ro.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-ru.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-sk.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-sl.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-sq.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-sr-SR.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-sr.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-sv.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-ta.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-th.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-tj.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-tr.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-uk.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-vi.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-zh-CN.js │ │ │ │ │ │ │ ├── jquery.ui.datepicker-zh-HK.js │ │ │ │ │ │ │ └── jquery.ui.datepicker-zh-TW.js │ │ │ │ │ │ ├── jquery.effects.blind.js │ │ │ │ │ │ ├── jquery.effects.bounce.js │ │ │ │ │ │ ├── jquery.effects.clip.js │ │ │ │ │ │ ├── jquery.effects.core.js │ │ │ │ │ │ ├── jquery.effects.drop.js │ │ │ │ │ │ ├── jquery.effects.explode.js │ │ │ │ │ │ ├── jquery.effects.fade.js │ │ │ │ │ │ ├── jquery.effects.fold.js │ │ │ │ │ │ ├── jquery.effects.highlight.js │ │ │ │ │ │ ├── jquery.effects.pulsate.js │ │ │ │ │ │ ├── jquery.effects.scale.js │ │ │ │ │ │ ├── jquery.effects.shake.js │ │ │ │ │ │ ├── jquery.effects.slide.js │ │ │ │ │ │ ├── jquery.effects.transfer.js │ │ │ │ │ │ ├── jquery.ui.accordion.js │ │ │ │ │ │ ├── jquery.ui.autocomplete.js │ │ │ │ │ │ ├── jquery.ui.button.js │ │ │ │ │ │ ├── jquery.ui.core.js │ │ │ │ │ │ ├── jquery.ui.datepicker.js │ │ │ │ │ │ ├── jquery.ui.dialog.js │ │ │ │ │ │ ├── jquery.ui.draggable.js │ │ │ │ │ │ ├── jquery.ui.droppable.js │ │ │ │ │ │ ├── jquery.ui.menu.js │ │ │ │ │ │ ├── jquery.ui.mouse.js │ │ │ │ │ │ ├── jquery.ui.position.js │ │ │ │ │ │ ├── jquery.ui.progressbar.js │ │ │ │ │ │ ├── jquery.ui.resizable.js │ │ │ │ │ │ ├── jquery.ui.selectable.js │ │ │ │ │ │ ├── jquery.ui.slider.js │ │ │ │ │ │ ├── jquery.ui.sortable.js │ │ │ │ │ │ ├── jquery.ui.spinner.js │ │ │ │ │ │ ├── jquery.ui.tabs.js │ │ │ │ │ │ ├── jquery.ui.tooltip.js │ │ │ │ │ │ └── jquery.ui.widget.js │ │ │ │ │ └── syntax/ │ │ │ │ │ ├── file1.js │ │ │ │ │ ├── file2.js │ │ │ │ │ ├── test.html │ │ │ │ │ ├── test1inline.js │ │ │ │ │ ├── test1main.js │ │ │ │ │ └── tokens.js │ │ │ │ └── unittests.js │ │ │ ├── JavaScriptRefactoring/ │ │ │ │ ├── ExtractToFunction.js │ │ │ │ ├── ExtractToVariable.js │ │ │ │ ├── RefactoringUtils.js │ │ │ │ ├── RenameIdentifier.js │ │ │ │ ├── Templates.json │ │ │ │ ├── WrapSelection.js │ │ │ │ ├── keyboard.json │ │ │ │ ├── main.js │ │ │ │ ├── unittest-files/ │ │ │ │ │ └── test.js │ │ │ │ └── unittests.js │ │ │ ├── LESSSupport/ │ │ │ │ ├── main.js │ │ │ │ └── unittests.js │ │ │ ├── LightTheme/ │ │ │ │ ├── main.less │ │ │ │ └── package.json │ │ │ ├── MDNDocs/ │ │ │ │ ├── InlineDocsViewer.html │ │ │ │ ├── InlineDocsViewer.js │ │ │ │ ├── LICENSE │ │ │ │ ├── MDNDocs.less │ │ │ │ ├── README.md │ │ │ │ ├── css.json │ │ │ │ ├── html.json │ │ │ │ ├── main.js │ │ │ │ ├── unittest-files/ │ │ │ │ │ ├── test1.css │ │ │ │ │ └── test1.html │ │ │ │ └── unittests.js │ │ │ ├── NavigationAndHistory/ │ │ │ │ ├── NavigationProvider.js │ │ │ │ ├── html/ │ │ │ │ │ └── recentfiles-template.html │ │ │ │ ├── keyboard.json │ │ │ │ ├── main.js │ │ │ │ └── styles/ │ │ │ │ └── recent-files.css │ │ │ ├── NoDistractions/ │ │ │ │ └── main.js │ │ │ ├── OpenWithExternalApplication/ │ │ │ │ ├── GraphicsFile.js │ │ │ │ ├── main.js │ │ │ │ └── node/ │ │ │ │ ├── OpenWithExternalApplicationDomain.js │ │ │ │ └── package.json │ │ │ ├── PhpTooling/ │ │ │ │ ├── CodeHintsProvider.js │ │ │ │ ├── PHPSymbolProviders.js │ │ │ │ ├── client.js │ │ │ │ ├── composer.json │ │ │ │ ├── main.js │ │ │ │ ├── package.json │ │ │ │ ├── phpGlobals.json │ │ │ │ ├── unittest-files/ │ │ │ │ │ ├── mac/ │ │ │ │ │ │ └── invalidphp │ │ │ │ │ └── test/ │ │ │ │ │ ├── test1.php │ │ │ │ │ ├── test2.php │ │ │ │ │ ├── test3.php │ │ │ │ │ └── test4.php │ │ │ │ └── unittests.js │ │ │ ├── PrefsCodeHints/ │ │ │ │ ├── main.js │ │ │ │ ├── styles/ │ │ │ │ │ └── brackets-prefs-hints.css │ │ │ │ ├── unittest-files/ │ │ │ │ │ └── preferences.json │ │ │ │ └── unittests.js │ │ │ ├── QuickOpenCSS/ │ │ │ │ └── main.js │ │ │ ├── QuickOpenHTML/ │ │ │ │ └── main.js │ │ │ ├── QuickOpenJavaScript/ │ │ │ │ └── main.js │ │ │ ├── QuickView/ │ │ │ │ ├── QuickView.less │ │ │ │ ├── QuickViewTemplate.html │ │ │ │ ├── main.js │ │ │ │ ├── unittest-files/ │ │ │ │ │ ├── test.css │ │ │ │ │ └── test.js │ │ │ │ └── unittests.js │ │ │ ├── README.md │ │ │ ├── RecentProjects/ │ │ │ │ ├── htmlContent/ │ │ │ │ │ └── projects-menu.html │ │ │ │ ├── keyboard.json │ │ │ │ ├── main.js │ │ │ │ ├── styles/ │ │ │ │ │ └── styles.less │ │ │ │ └── unittests.js │ │ │ ├── RemoteFileAdapter/ │ │ │ │ ├── RemoteFile.js │ │ │ │ ├── main.js │ │ │ │ ├── styles.css │ │ │ │ └── unittests.js │ │ │ ├── SVGCodeHints/ │ │ │ │ ├── SVGAttributes.json │ │ │ │ ├── SVGTags.json │ │ │ │ ├── main.js │ │ │ │ ├── styles/ │ │ │ │ │ └── brackets-svg-hints.css │ │ │ │ └── unittests.js │ │ │ ├── StaticServer/ │ │ │ │ ├── StaticServer.js │ │ │ │ ├── main.js │ │ │ │ ├── node/ │ │ │ │ │ └── StaticServerDomain.js │ │ │ │ ├── unittest-files/ │ │ │ │ │ ├── folder1/ │ │ │ │ │ │ └── index.txt │ │ │ │ │ └── folder2/ │ │ │ │ │ └── index.txt │ │ │ │ └── unittests.js │ │ │ └── UrlCodeHints/ │ │ │ ├── data.json │ │ │ ├── main.js │ │ │ ├── testfiles/ │ │ │ │ ├── subfolder/ │ │ │ │ │ ├── test.css │ │ │ │ │ ├── test.js │ │ │ │ │ └── test.scss │ │ │ │ └── test.html │ │ │ └── unittests.js │ │ ├── dev/ │ │ │ └── README.md │ │ └── samples/ │ │ ├── BracketsConfigCentral/ │ │ │ ├── htmlContent/ │ │ │ │ └── Config.html │ │ │ ├── main.js │ │ │ ├── package.json │ │ │ └── styles/ │ │ │ └── styles.css │ │ ├── ContextMenuTest/ │ │ │ └── main.js │ │ ├── InlineImageViewer/ │ │ │ ├── InlineImageViewer.html │ │ │ ├── InlineImageViewer.js │ │ │ ├── main.js │ │ │ └── style.css │ │ ├── LocalizationExample/ │ │ │ ├── README.md │ │ │ ├── htmlContent/ │ │ │ │ └── sampleHTMLFragment.html │ │ │ ├── main.js │ │ │ ├── nls/ │ │ │ │ ├── fr/ │ │ │ │ │ └── strings.js │ │ │ │ ├── root/ │ │ │ │ │ └── strings.js │ │ │ │ └── strings.js │ │ │ ├── package.json │ │ │ └── strings.js │ │ ├── README.md │ │ ├── TypingSpeedLogger/ │ │ │ └── main.js │ │ └── circular_dependency_test/ │ │ ├── main.js │ │ └── secondary.js │ ├── features/ │ │ ├── FindReferencesManager.js │ │ ├── JumpToDefManager.js │ │ ├── ParameterHintsManager.js │ │ └── PriorityBasedRegistration.js │ ├── file/ │ │ └── FileUtils.js │ ├── filesystem/ │ │ ├── Directory.js │ │ ├── File.js │ │ ├── FileIndex.js │ │ ├── FileSystem.js │ │ ├── FileSystemEntry.js │ │ ├── FileSystemError.js │ │ ├── FileSystemStats.js │ │ ├── WatchedRoot.js │ │ └── impls/ │ │ └── appshell/ │ │ ├── AppshellFileSystem.js │ │ └── node/ │ │ ├── CSharpWatcher.js │ │ ├── ChokidarWatcher.js │ │ ├── FileWatcherDomain.js │ │ ├── FileWatcherManager.js │ │ └── win32/ │ │ ├── CodeHelper.md │ │ └── LICENSE │ ├── help/ │ │ └── HelpCommandHandlers.js │ ├── htmlContent/ │ │ ├── about-dialog.html │ │ ├── code-hint-list.html │ │ ├── contributors-list.html │ │ ├── dialog-template.html │ │ ├── edit-filter-dialog.html │ │ ├── extension-manager-dialog.html │ │ ├── extension-manager-view-item.html │ │ ├── filter-name.html │ │ ├── findreplace-bar.html │ │ ├── image-view.html │ │ ├── infobar-template.html │ │ ├── inline-menu.html │ │ ├── install-extension-dialog.html │ │ ├── main-view.html │ │ ├── pane.html │ │ ├── parameter-hint-template.html │ │ ├── problems-panel-table.html │ │ ├── problems-panel.html │ │ ├── project-settings-dialog.html │ │ ├── search-panel.html │ │ ├── search-results.html │ │ ├── search-summary.html │ │ ├── themes-settings.html │ │ ├── update-dialog.html │ │ ├── update-list.html │ │ └── working-set.html │ ├── index.html │ ├── language/ │ │ ├── CSSUtils.js │ │ ├── CodeInspection.js │ │ ├── HTMLDOMDiff.js │ │ ├── HTMLInstrumentation.js │ │ ├── HTMLSimpleDOM.js │ │ ├── HTMLTokenizer.js │ │ ├── HTMLUtils.js │ │ ├── JSONUtils.js │ │ ├── JSUtils.js │ │ ├── LanguageManager.js │ │ ├── XMLUtils.js │ │ └── languages.json │ ├── languageTools/ │ │ ├── BracketsToNodeInterface.js │ │ ├── ClientLoader.js │ │ ├── DefaultEventHandlers.js │ │ ├── DefaultProviders.js │ │ ├── LanguageClient/ │ │ │ ├── Connection.js │ │ │ ├── LanguageClient.js │ │ │ ├── NodeToBracketsInterface.js │ │ │ ├── ProtocolAdapter.js │ │ │ ├── ServerUtils.js │ │ │ ├── Utils.js │ │ │ └── package.json │ │ ├── LanguageClientWrapper.js │ │ ├── LanguageTools.js │ │ ├── PathConverters.js │ │ ├── ToolingInfo.json │ │ ├── node/ │ │ │ └── RegisterLanguageClientInfo.js │ │ └── styles/ │ │ └── default_provider_style.css │ ├── main.js │ ├── nls/ │ │ ├── README.md │ │ ├── bg/ │ │ │ ├── strings.js │ │ │ └── urls.js │ │ ├── cs/ │ │ │ ├── strings.js │ │ │ └── urls.js │ │ ├── da/ │ │ │ ├── strings.js │ │ │ └── urls.js │ │ ├── de/ │ │ │ ├── strings.js │ │ │ └── urls.js │ │ ├── el/ │ │ │ ├── strings.js │ │ │ └── urls.js │ │ ├── en-gb/ │ │ │ └── strings.js │ │ ├── es/ │ │ │ ├── strings.js │ │ │ └── urls.js │ │ ├── fa-ir/ │ │ │ ├── strings.js │ │ │ └── urls.js │ │ ├── fi/ │ │ │ ├── strings.js │ │ │ └── urls.js │ │ ├── fr/ │ │ │ ├── strings.js │ │ │ └── urls.js │ │ ├── gl/ │ │ │ └── strings.js │ │ ├── hr/ │ │ │ ├── strings.js │ │ │ └── urls.js │ │ ├── hu/ │ │ │ └── strings.js │ │ ├── id/ │ │ │ ├── strings.js │ │ │ └── urls.js │ │ ├── it/ │ │ │ ├── strings.js │ │ │ └── urls.js │ │ ├── ja/ │ │ │ ├── strings.js │ │ │ └── urls.js │ │ ├── ko/ │ │ │ ├── .strings.js.swp │ │ │ ├── strings.js │ │ │ └── urls.js │ │ ├── lv/ │ │ │ └── strings.js │ │ ├── nb/ │ │ │ ├── strings.js │ │ │ └── urls.js │ │ ├── nl/ │ │ │ ├── strings.js │ │ │ └── urls.js │ │ ├── pl/ │ │ │ ├── strings.js │ │ │ └── urls.js │ │ ├── pt-br/ │ │ │ ├── strings.js │ │ │ └── urls.js │ │ ├── pt-pt/ │ │ │ ├── strings.js │ │ │ └── urls.js │ │ ├── ro/ │ │ │ └── strings.js │ │ ├── root/ │ │ │ ├── strings-app.js │ │ │ ├── strings.js │ │ │ └── urls.js │ │ ├── ru/ │ │ │ ├── strings.js │ │ │ └── urls.js │ │ ├── sk/ │ │ │ └── strings.js │ │ ├── sr/ │ │ │ ├── strings-app.js │ │ │ └── strings.js │ │ ├── strings-app.js │ │ ├── strings.js │ │ ├── sv/ │ │ │ ├── strings.js │ │ │ └── urls.js │ │ ├── tr/ │ │ │ ├── strings.js │ │ │ └── urls.js │ │ ├── uk/ │ │ │ ├── strings.js │ │ │ └── urls.js │ │ ├── urls.js │ │ ├── zh-cn/ │ │ │ ├── strings.js │ │ │ └── urls.js │ │ └── zh-tw/ │ │ ├── strings.js │ │ └── urls.js │ ├── npm-shrinkwrap.json │ ├── package.json │ ├── preferences/ │ │ ├── PreferencesBase.js │ │ ├── PreferencesDialogs.js │ │ ├── PreferencesImpl.js │ │ └── PreferencesManager.js │ ├── project/ │ │ ├── FileSyncManager.js │ │ ├── FileTreeView.js │ │ ├── FileTreeViewModel.js │ │ ├── FileViewController.js │ │ ├── ProjectManager.js │ │ ├── ProjectModel.js │ │ ├── SidebarView.js │ │ ├── WorkingSetSort.js │ │ └── WorkingSetView.js │ ├── search/ │ │ ├── FileFilters.js │ │ ├── FindBar.js │ │ ├── FindInFiles.js │ │ ├── FindInFilesUI.js │ │ ├── FindReplace.js │ │ ├── FindUtils.js │ │ ├── QuickOpen.js │ │ ├── QuickOpenHelper.js │ │ ├── QuickSearchField.js │ │ ├── ScrollTrackMarkers.js │ │ ├── SearchModel.js │ │ ├── SearchResultsView.js │ │ └── node/ │ │ └── FindInFilesDomain.js │ ├── strings.js │ ├── styles/ │ │ ├── Makefile │ │ ├── bootstrap/ │ │ │ ├── accordion.less │ │ │ ├── alerts.less │ │ │ ├── bootstrap.less │ │ │ ├── breadcrumbs.less │ │ │ ├── button-groups.less │ │ │ ├── buttons.less │ │ │ ├── carousel.less │ │ │ ├── close.less │ │ │ ├── code.less │ │ │ ├── component-animations.less │ │ │ ├── dropdowns.less │ │ │ ├── forms.less │ │ │ ├── grid.less │ │ │ ├── hero-unit.less │ │ │ ├── labels-badges.less │ │ │ ├── layouts.less │ │ │ ├── media.less │ │ │ ├── mixins.less │ │ │ ├── modals.less │ │ │ ├── navbar.less │ │ │ ├── navs.less │ │ │ ├── pager.less │ │ │ ├── pagination.less │ │ │ ├── popovers.less │ │ │ ├── progress-bars.less │ │ │ ├── reset.less │ │ │ ├── scaffolding.less │ │ │ ├── sprites.less │ │ │ ├── tables.less │ │ │ ├── thumbnails.less │ │ │ ├── tooltip.less │ │ │ ├── type.less │ │ │ ├── utilities.less │ │ │ ├── variables.less │ │ │ └── wells.less │ │ ├── brackets.less │ │ ├── brackets_codemirror_override.less │ │ ├── brackets_core_ui_variables.less │ │ ├── brackets_fonts.less │ │ ├── brackets_mixins.less │ │ ├── brackets_patterns_override.less │ │ ├── brackets_scrollbars.less │ │ ├── brackets_shared.less │ │ ├── brackets_theme_default.less │ │ ├── brackets_variables.less │ │ ├── infobar-styles.less │ │ └── jsTreeTheme.less │ ├── supported-encodings.json │ ├── thirdparty/ │ │ ├── classnames.js │ │ ├── globmatch.js │ │ ├── immutable.js │ │ ├── jquery-2.1.3.js │ │ ├── lodash.js │ │ └── murmurhash3_gc.js │ ├── utils/ │ │ ├── AnimationUtils.js │ │ ├── AppInit.js │ │ ├── Async.js │ │ ├── BuildInfoUtils.js │ │ ├── ColorUtils.js │ │ ├── Compatibility.js │ │ ├── DeprecationWarning.js │ │ ├── DragAndDrop.js │ │ ├── DropdownEventHandler.js │ │ ├── EventDispatcher.js │ │ ├── ExtensionLoader.js │ │ ├── ExtensionUtils.js │ │ ├── Global.js │ │ ├── HealthLogger.js │ │ ├── KeyEvent.js │ │ ├── LocalizationUtils.js │ │ ├── NativeApp.js │ │ ├── NodeConnection.js │ │ ├── NodeDomain.js │ │ ├── PerfUtils.js │ │ ├── Resizer.js │ │ ├── ShellAPI.js │ │ ├── StringMatch.js │ │ ├── StringUtils.js │ │ ├── TokenUtils.js │ │ ├── UpdateNotification.js │ │ ├── UrlParams.js │ │ ├── ValidationUtils.js │ │ └── ViewUtils.js │ ├── view/ │ │ ├── MainViewFactory.js │ │ ├── MainViewManager.js │ │ ├── Pane.js │ │ ├── ThemeManager.js │ │ ├── ThemeSettings.js │ │ ├── ThemeView.js │ │ ├── ViewCommandHandlers.js │ │ ├── ViewStateManager.js │ │ ├── WorkspaceManager.js │ │ └── fontrules/ │ │ └── font-based-rules.less │ ├── widgets/ │ │ ├── DefaultDialogs.js │ │ ├── Dialogs.js │ │ ├── DropdownButton.js │ │ ├── InlineMenu.js │ │ ├── ModalBar.js │ │ ├── PopUpManager.js │ │ ├── StatusBar.html │ │ ├── StatusBar.js │ │ ├── bootstrap-alerts.js │ │ ├── bootstrap-button.js │ │ ├── bootstrap-dropdown.js │ │ ├── bootstrap-modal.js │ │ ├── bootstrap-popover.js │ │ ├── bootstrap-scrollspy.js │ │ ├── bootstrap-tab.js │ │ ├── bootstrap-tooltip.js │ │ ├── bootstrap-twipsy-mod.js │ │ └── infobar.js │ └── xorigin.js ├── tasks/ │ ├── build.js │ ├── cla-exceptions.json │ ├── lib/ │ │ └── common.js │ ├── npm-install.js │ ├── pack-web-dependencies.js │ ├── test.js │ ├── update-release-number.js │ └── write-config.js ├── test/ │ ├── BootstrapReporterView.css │ ├── BootstrapReporterView.js │ ├── PerformanceTestSuite.js │ ├── README.md │ ├── SpecRunner.html │ ├── SpecRunner.js │ ├── TestPreferencesImpl.js │ ├── UnitTestReporter.js │ ├── UnitTestSuite.js │ ├── jasmine.sh │ ├── node/ │ │ └── TestingDomain.js │ ├── perf/ │ │ ├── OpenFile-perf-files/ │ │ │ ├── England(Chinese).htm │ │ │ ├── InlineWidget.js │ │ │ ├── blank.js │ │ │ ├── brackets-concat.js │ │ │ ├── jquery.mobile-1.1.0.css │ │ │ ├── jquery.mobile-1.1.0.js │ │ │ ├── jquery_ui_index.html │ │ │ └── quiet-scrollbars.css │ │ └── Performance-test.js │ ├── polyfills.js │ ├── smokes/ │ │ ├── citrus completed/ │ │ │ ├── css/ │ │ │ │ ├── citrus_mq.css │ │ │ │ ├── desktop.css │ │ │ │ ├── phone.css │ │ │ │ └── tablet.css │ │ │ └── index.html │ │ └── server-tests/ │ │ ├── css/ │ │ │ ├── citrus_mq.css │ │ │ ├── desktop.css │ │ │ ├── phone.css │ │ │ └── tablet.css │ │ ├── pathRel.html │ │ ├── pathRoot.html │ │ └── server.php │ ├── spec/ │ │ ├── Async-test.js │ │ ├── CSSInlineEdit-test-files/ │ │ │ ├── css/ │ │ │ │ ├── test.css │ │ │ │ └── test2.css │ │ │ ├── index-css.html │ │ │ ├── index-less.html │ │ │ ├── less/ │ │ │ │ ├── test.less │ │ │ │ └── test2.less │ │ │ └── scss/ │ │ │ └── test.scss │ │ ├── CSSInlineEdit-test.js │ │ ├── CSSUtils-test-files/ │ │ │ ├── bootstrap.css │ │ │ ├── contexts.css │ │ │ ├── edit.js │ │ │ ├── embedded.html │ │ │ ├── escaped-identifiers.css │ │ │ ├── groups.css │ │ │ ├── include-mixin.scss │ │ │ ├── issue-403-test.css │ │ │ ├── mixins.less │ │ │ ├── navbar.scss │ │ │ ├── offsets.css │ │ │ ├── panels.less │ │ │ ├── parent-selector.less │ │ │ ├── print.less │ │ │ ├── property-list.css │ │ │ ├── ranges.css │ │ │ ├── regions.css │ │ │ ├── selector-positions.css │ │ │ ├── simple.css │ │ │ ├── sprint4.css │ │ │ ├── table&button.scss │ │ │ ├── universal.css │ │ │ ├── variables.less │ │ │ └── variables.scss │ │ ├── CSSUtils-test.js │ │ ├── CodeHint-test-files/ │ │ │ ├── test.clj │ │ │ ├── test1.html │ │ │ └── testRegExp.js │ │ ├── CodeHint-test.js │ │ ├── CodeHintUtils-test.js │ │ ├── CodeInspection-test-files/ │ │ │ ├── errors.css │ │ │ ├── errors.js │ │ │ └── no-errors.js │ │ ├── CodeInspection-test.js │ │ ├── CommandManager-test.js │ │ ├── Document-test-files/ │ │ │ ├── test.css │ │ │ ├── test.html │ │ │ └── test.js │ │ ├── Document-test.js │ │ ├── DocumentCommandHandlers-test-files/ │ │ │ ├── test.js │ │ │ └── test2.js │ │ ├── DocumentCommandHandlers-test.js │ │ ├── DocumentManager-test.js │ │ ├── DragAndDrop-test.js │ │ ├── Editor-test.js │ │ ├── EditorCommandHandlers-test-files/ │ │ │ ├── test.css │ │ │ ├── test.html │ │ │ └── test.js │ │ ├── EditorCommandHandlers-test.js │ │ ├── EditorManager-test.js │ │ ├── EditorOptionHandlers-test-files/ │ │ │ ├── test.css │ │ │ ├── test.html │ │ │ └── test.js │ │ ├── EditorOptionHandlers-test.js │ │ ├── EditorRedraw-test.js │ │ ├── EventDispatcher-test.js │ │ ├── ExtensionInstallation-test.js │ │ ├── ExtensionLoader-test-files/ │ │ │ ├── BadRequire/ │ │ │ │ └── main.js │ │ │ ├── BadRequireConfig/ │ │ │ │ └── requirejs-config.json │ │ │ ├── InitFail/ │ │ │ │ └── main.js │ │ │ ├── InitFailWithError/ │ │ │ │ └── main.js │ │ │ ├── InitFailWithErrorAsync/ │ │ │ │ └── main.js │ │ │ ├── InitResolved/ │ │ │ │ └── main.js │ │ │ ├── InitResolvedAsync/ │ │ │ │ └── main.js │ │ │ ├── InitRuntimeError/ │ │ │ │ └── main.js │ │ │ ├── InitTimeout/ │ │ │ │ └── main.js │ │ │ ├── NoInit/ │ │ │ │ └── main.js │ │ │ └── RequireJSConfig/ │ │ │ ├── bar.js │ │ │ ├── main.js │ │ │ └── requirejs-config.json │ │ ├── ExtensionLoader-test.js │ │ ├── ExtensionManager-test-files/ │ │ │ ├── auto-install-extensions1/ │ │ │ │ └── should-be-ignored.txt │ │ │ ├── default/ │ │ │ │ └── mock-extension-1/ │ │ │ │ ├── main.js │ │ │ │ └── package.json │ │ │ ├── dev/ │ │ │ │ └── mock-extension-2/ │ │ │ │ ├── main.js │ │ │ │ └── package.json │ │ │ ├── mockExtensionList.json │ │ │ ├── mockRegistry.json │ │ │ ├── mockRegistryForSearch.json │ │ │ ├── mockRegistryThemes.json │ │ │ └── user/ │ │ │ ├── install-later-extension/ │ │ │ │ ├── main.js │ │ │ │ └── package.json │ │ │ ├── mock-extension-3/ │ │ │ │ ├── main.js │ │ │ │ └── package.json │ │ │ ├── mock-extension-4/ │ │ │ │ ├── main.js │ │ │ │ └── package.json │ │ │ └── mock-legacy-extension/ │ │ │ └── main.js │ │ ├── ExtensionManager-test.js │ │ ├── ExtensionUtils-test-files/ │ │ │ ├── bad-import.css │ │ │ ├── basic.css │ │ │ ├── basic.less │ │ │ ├── less.text │ │ │ └── sub dir/ │ │ │ ├── fifth.less │ │ │ ├── fourth.css │ │ │ ├── fourth.less │ │ │ ├── second.css │ │ │ └── third.css │ │ ├── ExtensionUtils-test.js │ │ ├── FileFilters-test.js │ │ ├── FileSystem-test.js │ │ ├── FileTreeView-test.js │ │ ├── FileTreeViewModel-test.js │ │ ├── FileUtils-test.js │ │ ├── FindInFiles-test.js │ │ ├── FindReplace-known-goods/ │ │ │ ├── changed-file/ │ │ │ │ ├── bar.txt │ │ │ │ ├── css/ │ │ │ │ │ └── foo.css │ │ │ │ ├── foo.html │ │ │ │ └── foo.js │ │ │ ├── regexp-case-insensitive/ │ │ │ │ ├── bar.txt │ │ │ │ ├── css/ │ │ │ │ │ └── foo.css │ │ │ │ ├── foo.html │ │ │ │ └── foo.js │ │ │ ├── regexp-case-sensitive/ │ │ │ │ ├── bar.txt │ │ │ │ ├── css/ │ │ │ │ │ └── foo.css │ │ │ │ ├── foo.html │ │ │ │ └── foo.js │ │ │ ├── regexp-dollar-replace/ │ │ │ │ ├── bar.txt │ │ │ │ ├── css/ │ │ │ │ │ └── foo.css │ │ │ │ ├── foo.html │ │ │ │ └── foo.js │ │ │ ├── regexp-replace-multiline/ │ │ │ │ ├── bar.txt │ │ │ │ ├── css/ │ │ │ │ │ └── foo.css │ │ │ │ ├── foo.html │ │ │ │ └── foo.js │ │ │ ├── regexp-replace-multiline-partial/ │ │ │ │ ├── bar.txt │ │ │ │ ├── css/ │ │ │ │ │ └── foo.css │ │ │ │ ├── foo.html │ │ │ │ └── foo.js │ │ │ ├── regexp-zero-length/ │ │ │ │ ├── bar.txt │ │ │ │ ├── css/ │ │ │ │ │ └── foo.css │ │ │ │ ├── foo.html │ │ │ │ └── foo.js │ │ │ ├── simple-case-insensitive/ │ │ │ │ ├── bar.txt │ │ │ │ ├── css/ │ │ │ │ │ └── foo.css │ │ │ │ ├── foo.html │ │ │ │ └── foo.js │ │ │ ├── simple-case-insensitive-except-foo.css/ │ │ │ │ ├── bar.txt │ │ │ │ ├── css/ │ │ │ │ │ └── foo.css │ │ │ │ ├── foo.html │ │ │ │ └── foo.js │ │ │ ├── simple-case-insensitive-large/ │ │ │ │ ├── bar.txt │ │ │ │ ├── bar2.txt │ │ │ │ ├── bar3.txt │ │ │ │ ├── bar4.txt │ │ │ │ ├── bar5.txt │ │ │ │ ├── bar6.txt │ │ │ │ ├── bar7.txt │ │ │ │ ├── css/ │ │ │ │ │ ├── foo.css │ │ │ │ │ ├── foo2.css │ │ │ │ │ ├── foo3.css │ │ │ │ │ ├── foo4.css │ │ │ │ │ ├── foo5.css │ │ │ │ │ ├── foo6.css │ │ │ │ │ └── foo7.css │ │ │ │ ├── foo.html │ │ │ │ ├── foo.js │ │ │ │ ├── foo2.html │ │ │ │ ├── foo2.js │ │ │ │ ├── foo3.html │ │ │ │ ├── foo3.js │ │ │ │ ├── foo4.html │ │ │ │ ├── foo4.js │ │ │ │ ├── foo5.html │ │ │ │ ├── foo5.js │ │ │ │ ├── foo6.html │ │ │ │ ├── foo6.js │ │ │ │ ├── foo7.html │ │ │ │ └── foo7.js │ │ │ ├── simple-case-insensitive-modified/ │ │ │ │ ├── bar.txt │ │ │ │ ├── css/ │ │ │ │ │ └── foo.css │ │ │ │ ├── foo.html │ │ │ │ └── foo.js │ │ │ ├── simple-case-insensitive-only-foo.css/ │ │ │ │ ├── bar.txt │ │ │ │ ├── css/ │ │ │ │ │ └── foo.css │ │ │ │ ├── foo.html │ │ │ │ └── foo.js │ │ │ ├── simple-case-insensitive-unchecked/ │ │ │ │ ├── bar.txt │ │ │ │ ├── css/ │ │ │ │ │ └── foo.css │ │ │ │ ├── foo.html │ │ │ │ └── foo.js │ │ │ ├── simple-case-sensitive/ │ │ │ │ ├── bar.txt │ │ │ │ ├── css/ │ │ │ │ │ └── foo.css │ │ │ │ ├── foo.html │ │ │ │ └── foo.js │ │ │ └── unchanged/ │ │ │ ├── bar.txt │ │ │ ├── css/ │ │ │ │ └── foo.css │ │ │ ├── foo.html │ │ │ └── foo.js │ │ ├── FindReplace-test-files/ │ │ │ ├── bar.txt │ │ │ ├── css/ │ │ │ │ └── foo.css │ │ │ ├── foo.html │ │ │ └── foo.js │ │ ├── FindReplace-test-files-large/ │ │ │ ├── bar.txt │ │ │ ├── bar2.txt │ │ │ ├── bar3.txt │ │ │ ├── bar4.txt │ │ │ ├── bar5.txt │ │ │ ├── bar6.txt │ │ │ ├── bar7.txt │ │ │ ├── css/ │ │ │ │ ├── foo.css │ │ │ │ ├── foo2.css │ │ │ │ ├── foo3.css │ │ │ │ ├── foo4.css │ │ │ │ ├── foo5.css │ │ │ │ ├── foo6.css │ │ │ │ └── foo7.css │ │ │ ├── foo.html │ │ │ ├── foo.js │ │ │ ├── foo2.html │ │ │ ├── foo2.js │ │ │ ├── foo3.html │ │ │ ├── foo3.js │ │ │ ├── foo4.html │ │ │ ├── foo4.js │ │ │ ├── foo5.html │ │ │ ├── foo5.js │ │ │ ├── foo6.html │ │ │ ├── foo6.js │ │ │ ├── foo7.html │ │ │ └── foo7.js │ │ ├── FindReplace-test-files-manyhits/ │ │ │ ├── manyhits-1.txt │ │ │ └── manyhits-2.txt │ │ ├── FindReplace-test.js │ │ ├── HTMLInstrumentation-test-files/ │ │ │ ├── REC-widgets-20121127.html │ │ │ ├── invalidHTML.html │ │ │ ├── omitEndTags.html │ │ │ └── wellformed.html │ │ ├── HTMLInstrumentation-test.js │ │ ├── HTMLSimpleDOM-test.js │ │ ├── HTMLTokenizer-test.js │ │ ├── InlineEditorProviders-test-files/ │ │ │ ├── test1.css │ │ │ ├── test1.html │ │ │ ├── test1.php │ │ │ └── testOneRuleFile.css │ │ ├── InlineEditorProviders-test.js │ │ ├── InstallExtensionDialog-test.js │ │ ├── JSONUtils-test.js │ │ ├── JSUtils-test-files/ │ │ │ ├── braceEnd.js │ │ │ ├── edit.js │ │ │ ├── eof.js │ │ │ ├── eof2.js │ │ │ ├── es6-async-arrow.js │ │ │ ├── es6-classes.js │ │ │ ├── es6-getter-setter.js │ │ │ ├── es6-inheritance.js │ │ │ ├── es6-static-methods.js │ │ │ ├── invalid.js │ │ │ ├── jquery-1.7.js │ │ │ ├── simple.js │ │ │ ├── test1inline.js │ │ │ ├── test1main.js │ │ │ └── tricky.js │ │ ├── JSUtils-test.js │ │ ├── KeyBindingManager-test-files/ │ │ │ ├── blank.json │ │ │ ├── duplicateShortcuts.json │ │ │ ├── empty.json │ │ │ ├── invalid.json │ │ │ ├── invalidKeys.json │ │ │ ├── keymap.json │ │ │ ├── keymap1.json │ │ │ ├── macKeymap.json │ │ │ ├── macKeymap1.json │ │ │ ├── macRestrictedShortcut.json │ │ │ ├── multipleShortcuts.json │ │ │ ├── reassignCopy.json │ │ │ ├── restrictedShortcut.json │ │ │ └── whitespace.json │ │ ├── KeyBindingManager-test.js │ │ ├── LanguageManager-test.js │ │ ├── LanguageTools-test-files/ │ │ │ ├── clients/ │ │ │ │ ├── CommunicationTestClient/ │ │ │ │ │ ├── client.js │ │ │ │ │ ├── main.js │ │ │ │ │ └── package.json │ │ │ │ ├── FeatureClient/ │ │ │ │ │ ├── client.js │ │ │ │ │ └── main.js │ │ │ │ ├── InterfaceTestClient/ │ │ │ │ │ ├── client.js │ │ │ │ │ └── main.js │ │ │ │ ├── LoadSimpleClient/ │ │ │ │ │ ├── client.js │ │ │ │ │ └── main.js │ │ │ │ ├── ModuleTestClient/ │ │ │ │ │ ├── client.js │ │ │ │ │ └── main.js │ │ │ │ └── OptionsTestClient/ │ │ │ │ ├── client.js │ │ │ │ └── main.js │ │ │ ├── project/ │ │ │ │ ├── sample1.txt │ │ │ │ └── sample2.txt │ │ │ └── server/ │ │ │ └── lsp-test-server/ │ │ │ ├── main.js │ │ │ └── package.json │ │ ├── LanguageTools-test.js │ │ ├── LiveDevelopment-MultiBrowser-test-files/ │ │ │ ├── import1.css │ │ │ ├── index.html │ │ │ ├── simple1.css │ │ │ ├── simple1.html │ │ │ ├── simple1.js │ │ │ ├── simpleShared.css │ │ │ ├── sub/ │ │ │ │ └── test.css │ │ │ └── withoutHead.html │ │ ├── LiveDevelopment-chrome-user-data/ │ │ │ ├── Default/ │ │ │ │ ├── Archived History │ │ │ │ ├── Bookmarks │ │ │ │ ├── Bookmarks.bak │ │ │ │ ├── Cookies │ │ │ │ ├── Current Session │ │ │ │ ├── Current Tabs │ │ │ │ ├── Favicons │ │ │ │ ├── History │ │ │ │ ├── History Index 2012-04 │ │ │ │ ├── History Provider Cache │ │ │ │ ├── Last Session │ │ │ │ ├── Last Tabs │ │ │ │ ├── Network Action Predictor │ │ │ │ ├── Preferences │ │ │ │ ├── Shortcuts │ │ │ │ ├── Top Sites │ │ │ │ ├── User StyleSheets/ │ │ │ │ │ └── Custom.css │ │ │ │ ├── Visited Links │ │ │ │ └── Web Data │ │ │ └── Local State │ │ ├── LiveDevelopment-test-files/ │ │ │ ├── dynamic-project-1/ │ │ │ │ └── sub/ │ │ │ │ └── sub2/ │ │ │ │ ├── index.php │ │ │ │ └── test.css │ │ │ ├── dynamic-project-2/ │ │ │ │ └── sub/ │ │ │ │ ├── index.php │ │ │ │ └── sub2/ │ │ │ │ └── test.css │ │ │ ├── dynamic-project-3/ │ │ │ │ ├── index.php │ │ │ │ └── sub/ │ │ │ │ └── sub2/ │ │ │ │ └── test.css │ │ │ ├── dynamic-project-4/ │ │ │ │ ├── index.php │ │ │ │ └── sub/ │ │ │ │ ├── index.php │ │ │ │ └── sub2/ │ │ │ │ ├── index.php │ │ │ │ └── test.css │ │ │ ├── dynamic-project-5/ │ │ │ │ ├── index.php │ │ │ │ └── sub/ │ │ │ │ ├── index.php │ │ │ │ ├── sub2/ │ │ │ │ │ └── index.php │ │ │ │ └── test.css │ │ │ ├── dynamic-project-6/ │ │ │ │ ├── top1/ │ │ │ │ │ └── index.php │ │ │ │ └── top2/ │ │ │ │ └── test.css │ │ │ ├── iframe.css │ │ │ ├── iframe.html │ │ │ ├── notlive.css │ │ │ ├── simple1.css │ │ │ ├── simple1.html │ │ │ ├── simple1.js │ │ │ ├── simple1Query.html │ │ │ ├── simple1iframe.html │ │ │ ├── simpleShared.css │ │ │ ├── static-project-1/ │ │ │ │ └── sub/ │ │ │ │ └── sub2/ │ │ │ │ ├── index.html │ │ │ │ └── test.css │ │ │ ├── static-project-2/ │ │ │ │ └── sub/ │ │ │ │ ├── index.html │ │ │ │ └── sub2/ │ │ │ │ └── test.css │ │ │ ├── static-project-3/ │ │ │ │ ├── index.html │ │ │ │ └── sub/ │ │ │ │ └── sub2/ │ │ │ │ └── test.css │ │ │ ├── static-project-4/ │ │ │ │ ├── index.html │ │ │ │ └── sub/ │ │ │ │ ├── index.html │ │ │ │ └── sub2/ │ │ │ │ ├── index.html │ │ │ │ └── test.css │ │ │ ├── static-project-5/ │ │ │ │ ├── index.html │ │ │ │ └── sub/ │ │ │ │ ├── index.html │ │ │ │ ├── sub2/ │ │ │ │ │ └── index.html │ │ │ │ └── test.css │ │ │ ├── static-project-6/ │ │ │ │ ├── top1/ │ │ │ │ │ └── index.html │ │ │ │ └── top2/ │ │ │ │ └── test.css │ │ │ └── test.xhtml │ │ ├── LiveDevelopment-test.js │ │ ├── LiveDevelopmentMultiBrowser-test.js │ │ ├── LowLevelFileIO-test-files/ │ │ │ ├── cant_read_here/ │ │ │ │ └── readme.txt │ │ │ ├── cant_write_here/ │ │ │ │ └── readme.txt │ │ │ ├── emptyfile.txt │ │ │ ├── es_small_utf8.html │ │ │ ├── file_one.txt │ │ │ ├── file_three.txt │ │ │ ├── file_two.txt │ │ │ ├── rename_me/ │ │ │ │ └── hello.txt │ │ │ ├── ru_bad_utf8.html │ │ │ ├── ru_utf16.html │ │ │ ├── ru_utf16_noBOM.html │ │ │ ├── ru_utf32.html │ │ │ ├── ru_utf32_noBOM.html │ │ │ ├── ru_utf8.html │ │ │ ├── ru_utf8_wBOM.html │ │ │ └── write_test.txt │ │ ├── LowLevelFileIO-test.js │ │ ├── MainViewFactory-test-files/ │ │ │ ├── css/ │ │ │ │ ├── citrus_mq.css │ │ │ │ ├── desktop.css │ │ │ │ ├── phone.css │ │ │ │ └── tablet.css │ │ │ └── index.html │ │ ├── MainViewFactory-test.js │ │ ├── MainViewManager-test-files/ │ │ │ ├── test.css │ │ │ ├── test.html │ │ │ └── test.js │ │ ├── MainViewManager-test.js │ │ ├── Menu-test.js │ │ ├── MockFileSystemImpl.js │ │ ├── MockFileSystemModel.js │ │ ├── MultiRangeInlineEditor-test.js │ │ ├── NativeMenu-test.js │ │ ├── NodeConnection-test-files/ │ │ │ ├── BinaryTestCommands.js │ │ │ ├── TestCommandsError.js │ │ │ ├── TestCommandsOne.js │ │ │ └── TestCommandsTwo.js │ │ ├── NodeConnection-test.js │ │ ├── Pane-test.js │ │ ├── PhantomHelper.js │ │ ├── PreferencesBase-test-files/ │ │ │ ├── .brackets.json │ │ │ └── empty.json │ │ ├── PreferencesBase-test.js │ │ ├── PreferencesManager-test.js │ │ ├── ProjectManager-test-files/ │ │ │ ├── directory/ │ │ │ │ └── interiorfile.js │ │ │ ├── file.js │ │ │ ├── git/ │ │ │ │ └── index │ │ │ └── toDelete1/ │ │ │ └── file.js │ │ ├── ProjectManager-test.js │ │ ├── ProjectModel-test.js │ │ ├── QuickOpen-test-files/ │ │ │ ├── lotsOfLines.html │ │ │ └── somelines.html │ │ ├── QuickOpen-test.js │ │ ├── QuickSearchField-test.js │ │ ├── RemoteFunctions-test.js │ │ ├── SpecRunnerUtils-test.js │ │ ├── SpecRunnerUtils.js │ │ ├── StringMatch-test.js │ │ ├── StringUtils-test.js │ │ ├── TextRange-test.js │ │ ├── Theme-test-files/ │ │ │ ├── empty.css │ │ │ ├── empty.less │ │ │ ├── empty.txt │ │ │ ├── scrollbars.css │ │ │ └── simple-scrollbars.css │ │ ├── ThemeManager-test.js │ │ ├── UpdateNotification-test-files/ │ │ │ ├── versionInfo.json │ │ │ ├── versionInfoBroken.json │ │ │ └── versionInfoXSS.json │ │ ├── UpdateNotification-test.js │ │ ├── UrlParams-test.js │ │ ├── ValidationUtils-test.js │ │ ├── ViewCommandHandlers-test-files/ │ │ │ ├── test.css │ │ │ └── test.html │ │ ├── ViewCommandHandlers-test.js │ │ ├── ViewFactory-test.js │ │ ├── ViewUtils-test.js │ │ ├── WorkingSetSort-test.js │ │ ├── WorkingSetView-test-files/ │ │ │ ├── directory/ │ │ │ │ ├── directory/ │ │ │ │ │ └── file_one.js │ │ │ │ └── file_one.js │ │ │ ├── file_four.html │ │ │ ├── file_one.js │ │ │ ├── file_three.js │ │ │ ├── file_two.js │ │ │ └── file_zero.css │ │ ├── WorkingSetView-test.js │ │ ├── XMLUtils-test.js │ │ └── extension-test-files/ │ │ └── basic-valid-theme-1.0/ │ │ ├── package.json │ │ └── theme.less │ └── thirdparty/ │ ├── bootstrap2/ │ │ ├── css/ │ │ │ ├── bootstrap-responsive.css │ │ │ └── bootstrap.css │ │ └── js/ │ │ └── bootstrap.js │ ├── jasmine-core/ │ │ ├── example/ │ │ │ ├── SpecRunner.html │ │ │ ├── spec/ │ │ │ │ ├── PlayerSpec.js │ │ │ │ └── SpecHelper.js │ │ │ └── src/ │ │ │ ├── Player.js │ │ │ └── Song.js │ │ ├── jasmine-html.js │ │ ├── jasmine.css │ │ ├── jasmine.js │ │ ├── json2.js │ │ ├── spec │ │ └── version.rb │ ├── jasmine-jquery-1.3.1.js │ ├── jasmine-reporters/ │ │ ├── LICENSE │ │ ├── README.markdown │ │ └── jasmine.junit_reporter.js │ └── jquery.mockjax.js └── tools/ ├── restore_installed_build.bat ├── restore_installed_build.sh ├── setup_for_hacking.bat ├── setup_for_hacking.sh ├── setup_server_smokes.bat └── setup_server_smokes.sh ================================================ FILE CONTENTS ================================================ ================================================ FILE: .brackets.json ================================================ { "jslint.options": { "vars": true, "plusplus": true, "browser": false, "devel": true, "nomen": true, "indent": 4, "maxerr": 50, "es5": true, "predef": [ "brackets", "require", "define", "$", "window", "setTimeout", "clearTimeout", "ArrayBuffer", "XMLHttpRequest", "Uint32Array", "WebSocket" ] }, "defaultExtension": "js", "language": { "javascript": { "linting.prefer": ["ESLint", "JSLint"], "linting.usePreferredOnly": true } }, "path": { "src/thirdparty/CodeMirror/**/*.js": { "spaceUnits": 2, "linting.enabled": false }, "src/thirdparty/globmatch.js": { "spaceUnits": 2, "linting.enabled": false } }, "spaceUnits": 4, "useTabChar": false } ================================================ FILE: .eslintignore ================================================ /src/extensions/default/brackets-eslint ================================================ FILE: .eslintrc.js ================================================ module.exports = { "rules": { // the rules below should be sorted in a same way they are sorted on http://eslint.org/docs/rules page // http://eslint.org/docs/rules/#possible-errors "no-caller": 2, "no-control-regex": 2, "no-empty": 1, "no-invalid-regexp": 2, "no-regex-spaces": 2, "no-unsafe-negation": 1, "valid-jsdoc": 0, "valid-typeof": 2, // http://eslint.org/docs/rules/#best-practices "curly": 2, "eqeqeq": [2, "smart"], "guard-for-in": 0, "no-else-return": 1, "no-fallthrough": 2, "no-invalid-this": 1, "no-iterator": 2, "no-loop-func": 2, "no-multi-str": 2, "no-new-func": 2, "no-new-wrappers": 2, "no-new": 2, "no-proto": 2, "no-redeclare": 1, "no-script-url": 2, "wrap-iife": [2, "outside"], // http://eslint.org/docs/rules/#strict-mode "strict": 2, // http://eslint.org/docs/rules/#variables "no-shadow-restricted-names": 2, "no-shadow": 1, "no-undef": 2, "no-unused-vars": [1, {"vars": "all", "args": "none"}], "no-use-before-define": 0, // http://eslint.org/docs/rules/#nodejs-and-commonjs "no-new-require": 2, // http://eslint.org/docs/rules/#stylistic-issues "block-spacing": 1, "brace-style": [1, "1tbs", { allowSingleLine: true }], "camelcase": 1, "comma-dangle": 2, "comma-spacing": 1, "comma-style": [1, "last"], "computed-property-spacing": 1, "eol-last": 1, "func-call-spacing": 1, "indent": [1, 4], "key-spacing": [1, { beforeColon: false, afterColon: true }], "max-len": [1, 120], "new-cap": [0, { "capIsNewExceptions": [ "$.Deferred", "$.Event", "CodeMirror.Pos", "Immutable.Map", "Immutable.List", "JSLINT" ] }], "new-parens": 2, "no-bitwise": 2, "no-new-object": 2, "no-trailing-spaces": 1, "semi-spacing": 1, "semi": 2 }, "globals": { "$": false, "brackets": false, "clearTimeout": false, "console": false, "define": false, "require": false, "setTimeout": false, "window": false, "ArrayBuffer": false, "Uint32Array": false, "WebSocket": false, "XMLHttpRequest": false }, "parserOptions": { "ecmaVersion": 6, "sourceType": "script", "ecmaFeatures": { "arrowFunctions": true, "binaryLiterals": true, "blockBindings": true, "classes": true } } }; ================================================ FILE: .gitattributes ================================================ # Auto detect text files and perform LF normalization * text=auto ================================================ FILE: .gitignore ================================================ Thumbs.db # ignore jenkins build info /build.prop # package-lock.json package-lock.json # ignore node_modules created by grunt, but not more deeply-nested node_modules /node_modules /npm-debug.log # ignore node_modules inside src /src/node_modules /src/JSUtils/node_modules /src/JSUtils/node/node_modules # ignore files copied from node_modules to src/thirdparty /src/thirdparty/CodeMirror /src/thirdparty/less.min.js /src/thirdparty/preact /src/thirdparty/preact-compat /src/thirdparty/preact-test-utils /src/thirdparty/simulate-event /src/thirdparty/xtend /src/thirdparty/acorn # ignore compiled files /dist /src/.index.html /src/styles/brackets.min.css /src/styles/brackets.min.css.map # ignore everything in the dev extension directory EXCEPT the README # (so that the directory is non-empty and can be in git) /src/extensions/dev/* !/src/extensions/dev/README.* /src/extensions/default/brackets-eslint /src/extensions/disabled # ignore .disabled file for default extensions /src/extensions/default/*/.disabled # generate through grunt /src/config.json #OSX .DS_Store files .DS_Store # unit test working directory /test/results /test/temp # Netbeans /nbproject # PhpStorm .idea # Files that can be automatically downloaded that we don't want to ship with our builds /src/extensibility/node/node_modules/request/tests/ # Files build by scripts /src/thirdparty/semver.browser.js ================================================ FILE: .gitmodules ================================================ [submodule "src/thirdparty/path-utils"] path = src/thirdparty/path-utils url = https://github.com/jblas/path-utils.git [submodule "src/thirdparty/mustache"] path = src/thirdparty/mustache url = https://github.com/janl/mustache.js.git [submodule "src/thirdparty/requirejs"] path = src/thirdparty/requirejs url = https://github.com/jrburke/requirejs.git [submodule "src/thirdparty/text"] path = src/thirdparty/text url = https://github.com/requirejs/text.git [submodule "src/thirdparty/i18n"] path = src/thirdparty/i18n url = https://github.com/requirejs/i18n.git [submodule "src/extensions/default/JSLint/thirdparty/jslint"] path = src/extensions/default/JSLint/thirdparty/jslint url = https://github.com/peterflynn/JSLint.git ================================================ FILE: .npmrc ================================================ package-lock=false ================================================ FILE: .travis.yml ================================================ language: node_js sudo: false # use container-based Travis infrastructure node_js: - "6" before_script: - npm install -g grunt-cli - npm install -g jasmine-node notifications: webhooks: urls: - https://webhooks.gitter.im/e/9c767842144fd24d26a5 on_success: change # options: [always|never|change] default: always on_failure: always # options: [always|never|change] default: always on_start: false # default: false branches: only: - master - release ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [admin@brackets.io](mailto:admin@brackets.io). All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ ================================================ FILE: CONTRIBUTING.md ================================================ # The Basics ### Filing a bug Check the [Troubleshooting Page](https://github.com/adobe/brackets/wiki/Troubleshooting) for common issues with installing & launching Brackets, using Live Preview, etc. **For bugs** be sure to search existing issues first. Include steps to consistently reproduce the problem, actual vs. expected results, and your OS and Brackets version number. Disable all extensions to verify the issue is a core Brackets bug. [Read more guidelines for filing good bugs...](https://github.com/adobe/brackets/wiki/How-to-Report-an-Issue) **For feature requests** please first check our [feature backlog](http://bit.ly/BracketsBacklog) to see if it's already there. You can upvote features you'd like to see. ### Submitting a pull request **Before you start coding**, post to the [brackets-dev Google group](http://groups.google.com/group/brackets-dev) or the [#brackets IRC channel on freenode](http://webchat.freenode.net/?channels=brackets) about what you're thinking of working on, so you can get early feedback. We don't want you to do tons of work and then have to rewrite half of it! For more on what's expected in a good pull request, see [Contributing Code](#contributing-code) below. # Ways to Contribute There are many ways you can contribute to the Brackets project: * **Fix a bug** or **implement a new feature** - read on below. * **[Write a Brackets extension](https://github.com/adobe/brackets/wiki/How-to-write-extensions)** and tell us about it! * **Test Brackets** and [report bugs](https://github.com/adobe/brackets/wiki/How-to-Report-an-Issue) you find. For sample testing steps, see [Brackets smoke tests](https://github.com/adobe/brackets/wiki/Brackets-Smoke-Tests), [smoke tests with a local server](https://github.com/adobe/brackets/wiki/Brackets-Server-Smoke-Tests), and [UI walkthrough steps](https://github.com/adobe/brackets/wiki/Localization-Tests). * **Write unit tests** for Brackets. * **Translate** Brackets into other languages (and help keep those translations up to date) - see [localization README](https://github.com/adobe/brackets/blob/master/src/nls/README.md). * **Write documentation** and help keep it up to date (the [How to Use Brackets](https://github.com/adobe/brackets/wiki/How-to-Use-Brackets) intro page is one example). * **Try out some [Brackets extensions](https://github.com/adobe/brackets/wiki/Brackets-Extensions)** and give feedback to their authors. ## Where Do I Start? To start editing the Brackets code, read **[How to Hack on Brackets](https://github.com/adobe/brackets/wiki/How-to-Hack-on-Brackets)**. To create your first Brackets extension, check out **[How to Write Extensions](https://github.com/adobe/brackets/wiki/How-to-write-extensions)**. Here are some ideas: * [Starter bugs](https://github.com/adobe/brackets/issues?labels=starter+bug&state=open) can provide a good intro to the Brackets code. * [Extension ideas](https://github.com/adobe/brackets/issues?q=label%3A%22Extension+Idea%22) are feature requests that we think would be best implemented as an add-on; it's up to the Brackets community to write them! * [Starter features](http://bit.ly/BracketsBacklog) are a bit larger in scope. Be sure to discuss these in the newsgroup before starting. _(To see starter features, click Filter Cards on the right and then click the green "Starter Feature" label)._ Once you're ready to start coding, see the next section, [Contributing Code](#contributing-code). **I'm new to JavaScript. How can I contribute to Brackets?** Brackets is a lot more complicated than the average website that uses JS. Better to start on some JS tutorials (like [Codecademy's](http://www.codecademy.com/tracks/javascript) or [MDN's](https://developer.mozilla.org/en-US/docs/JavaScript/Getting_Started)) and contribute in some of the other ways listed above. Testing is a great way to start thinking like a programmer before you've learned to code! ## Contributing Code To get started editing the Brackets code, read [How to Hack on Brackets](https://github.com/adobe/brackets/wiki/How-to-Hack-on-Brackets). Before submitting any pull request, please make sure to: 1. Discuss any major changes or questions beforehand in the [brackets-dev newsgroup](http://groups.google.com/group/brackets-dev). 2. Consider whether your change would be better as an optional extension. Brackets is lightweight and tightly focused - but highly extensible. 3. Follow the [Pull Request Checklist](https://github.com/adobe/brackets/wiki/Pull-Request-Checklist) to ensure a good-quality pull request. 4. Sign the [Brackets Contributor License Agreement](http://dev.brackets.io/brackets-contributor-license-agreement.html) (we cannot merge before this). High quality code and a top-notch user experience are very important in Brackets, and we carefully review pull requests to keep it that way. The better you follow the guidelines above, the more likely we are to accept your pull request - and the faster the code review will go. ## The Code Review Process Brackets committers are responsible for reviewing all pull requests, providing feedback, and ultimately merging good code into `master`. The review process ensures all code is high quality, maintainable, and well documented. Once you've opened a pull request, a committer will generally respond to it within a week with an initial set of comments (you don't need to ping anyone to find a reviewer). Some pull requests raise larger questions about UI design, product scope or architecture. Those are tagged to indicate that review will take longer: * \[PM\] - needs high-level input from product management * \[XD\] - needs UI design / visual design discussion * \[ARCH\] - needs architectural discussion The best way to avoid this sort of holdup is to discuss your changes on the newsgroup first! Once your pull request is merged, it will appear in the next release of Brackets - generally within two weeks. **Interested in becoming a committer?** See the [Committer Policy](https://github.com/adobe/brackets/wiki/Brackets-Committer-Policy) for details. Committers are expected to take a leading role in the project by making significant code contributions, reviewing pull requests, and providing feedback and suggestions on the direction of the project. Even if you're not a committer, you're still welcome to give feedback on any pull request! ================================================ FILE: Gruntfile.js ================================================ /* * Copyright (c) 2013 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /*eslint-env node */ /*jslint node: true */ 'use strict'; module.exports = function (grunt) { // load dependencies require('load-grunt-tasks')(grunt, { pattern: [ 'grunt-*', '!grunt-cli', '!grunt-lib-phantomjs', '!grunt-template-jasmine-requirejs' ] }); grunt.loadTasks('tasks'); // Project configuration. grunt.initConfig({ pkg : grunt.file.readJSON("package.json"), clean: { dist: { files: [{ dot: true, src: [ 'dist', 'src/.index.html', 'src/styles/brackets.css' ] }] }, node_modules_test_dir : { files: [{ dot: true, src: [ 'dist/node_modules/npm/test/fixtures', 'dist/node_modules/npm/node_modules/tar/test', 'dist/node_modules/npm/node_modules/npm-registry-client/test' ] }] } }, copy: { dist: { files: [ { 'dist/index.html': 'src/.index.html' }, /* static files */ { expand: true, dest: 'dist/', cwd: 'src/', src: [ 'nls/{,*/}*.js', 'xorigin.js', 'dependencies.js', 'thirdparty/requirejs/require.js', 'LiveDevelopment/launch.html', 'LiveDevelopment/transports/**', 'LiveDevelopment/MultiBrowserImpl/transports/**', 'LiveDevelopment/MultiBrowserImpl/launchers/**' ] }, /* node domains are not minified and must be copied to dist */ { expand: true, dest: 'dist/', cwd: 'src/', src: [ 'extensibility/node/**', 'JSUtils/node/**', 'languageTools/node/**', 'languageTools/styles/**', 'languageTools/LanguageClient/**', '!extensibility/node/spec/**', '!extensibility/node/node_modules/**/{test,tst}/**/*', '!extensibility/node/node_modules/**/examples/**/*', 'filesystem/impls/appshell/node/**', '!filesystem/impls/appshell/node/spec/**', 'search/node/**' ] }, /* extensions and CodeMirror modes */ { expand: true, dest: 'dist/', cwd: 'src/', src: [ 'extensions/default/**/*', '!extensions/default/*/unittest-files/**/*', '!extensions/default/*/unittests.js', '!extensions/default/{*/thirdparty,**/node_modules}/**/test/**/*', '!extensions/default/{*/thirdparty,**/node_modules}/**/doc/**/*', '!extensions/default/{*/thirdparty,**/node_modules}/**/examples/**/*', '!extensions/default/*/thirdparty/**/*.htm{,l}', 'extensions/dev/*', 'extensions/samples/**/*', 'thirdparty/CodeMirror/**', 'thirdparty/i18n/*.js', 'thirdparty/text/*.js' ] }, /* styles, fonts and images */ { expand: true, dest: 'dist/styles', cwd: 'src/styles', src: ['jsTreeTheme.css', 'fonts/{,*/}*.*', 'images/*', 'brackets.min.css*'] } ] }, thirdparty: { files: [ { expand: true, dest: 'src/thirdparty/CodeMirror', cwd: 'src/node_modules/codemirror', src: [ 'addon/{,*/}*', 'keymap/{,*/}*', 'lib/{,*/}*', 'mode/{,*/}*', 'theme/{,*/}*' ] }, { expand: true, flatten: true, dest: 'src/thirdparty', cwd: 'src/node_modules', src: [ 'less/dist/less.min.js' ] }, { expand: true, flatten: true, dest: 'src/thirdparty/preact-compat', cwd: 'src/node_modules/preact-compat', src: [ 'dist/preact-compat.min.js' ] }, { expand: true, flatten: true, dest: 'src/thirdparty/simulate-event', cwd: 'src/node_modules/simulate-event', src: [ 'simulate-event.js' ] }, { expand: true, flatten: true, dest: 'src/thirdparty/xtend', cwd: 'src/node_modules/xtend', src: [ 'mutable.js', 'immutable.js' ] }, { expand: true, dest: 'src/thirdparty/acorn', cwd: 'src/node_modules/acorn', src: [ 'dist/{,*/}*' ] } ] } }, cleanempty: { options: { force: true, files: false }, src: ['dist/**/*'] }, less: { dist: { files: { "src/styles/brackets.min.css": "src/styles/brackets.less" }, options: { compress: true, sourceMap: true, sourceMapFilename: 'src/styles/brackets.min.css.map', outputSourceFiles: true, sourceMapRootpath: '', sourceMapBasepath: 'src/styles' } } }, requirejs: { dist: { // Options: https://github.com/jrburke/r.js/blob/master/build/example.build.js options: { // `name` and `out` is set by grunt-usemin baseUrl: 'src', optimize: 'uglify2', // brackets.js should not be loaded until after polyfills defined in "utils/Compatibility" // so explicitly include it in main.js include: ["utils/Compatibility", "brackets"], // TODO: Figure out how to make sourcemaps work with grunt-usemin // https://github.com/yeoman/grunt-usemin/issues/30 generateSourceMaps: true, useSourceUrl: true, // required to support SourceMaps // http://requirejs.org/docs/errors.html#sourcemapcomments preserveLicenseComments: false, useStrict: true, // Disable closure, we want define/require to be globals wrap: false, exclude: ["text!config.json"], uglify2: {} // https://github.com/mishoo/UglifyJS2 } } }, targethtml: { dist: { files: { 'src/.index.html': 'src/index.html' } } }, useminPrepare: { options: { dest: 'dist' }, html: 'src/.index.html' }, usemin: { options: { dirs: ['dist'] }, html: ['dist/{,*/}*.html'] }, htmlmin: { dist: { options: { /*removeCommentsFromCDATA: true, // https://github.com/yeoman/grunt-usemin/issues/44 //collapseWhitespace: true, collapseBooleanAttributes: true, removeAttributeQuotes: true, removeRedundantAttributes: true, useShortDoctype: true, removeEmptyAttributes: true, removeOptionalTags: true*/ }, files: [{ expand: true, cwd: 'src', src: '*.html', dest: 'dist' }] } }, meta : { src : [ 'src/**/*.js', '!src/thirdparty/**', '!src/widgets/bootstrap-*.js', '!src/extensions/**/unittest-files/**/*.js', '!src/extensions/**/thirdparty/**/*.js', '!src/extensions/dev/**', '!src/extensions/disabled/**', '!**/node_modules/**/*.js', '!src/**/*-min.js', '!src/**/*.min.js' ], test : [ 'test/**/*.js', '!test/perf/*-files/**/*.js', '!test/spec/*-files/**/*.js', '!test/spec/*-known-goods/**/*.js', '!test/spec/FindReplace-test-files-*/**/*.js', '!test/smokes/**', '!test/temp/**', '!test/thirdparty/**', '!test/**/node_modules/**/*.js' ], grunt: [ 'Gruntfile.js', 'tasks/**/*.js' ], /* specs that can run in phantom.js */ specs : [ 'test/spec/CommandManager-test.js', //'test/spec/LanguageManager-test.js', //'test/spec/PreferencesManager-test.js', 'test/spec/ViewUtils-test.js' ] }, watch: { grunt: { files: ['<%= meta.grunt %>'], tasks: ['eslint:grunt'] }, src: { files: ['<%= meta.src %>'], tasks: ['eslint:src'] }, test: { files: ['<%= meta.test %>'], tasks: ['eslint:test'] }, options: { spawn: false } }, /* FIXME (jasonsanjose): how to handle extension tests */ jasmine : { src : 'undefined.js', /* trick the default runner to run without importing src files */ options : { junit : { path: 'test/results', consolidate: true }, specs : '<%= meta.specs %>', /* Keep in sync with test/SpecRunner.html dependencies */ vendor : [ // For reference to why this polyfill is needed see Issue #7951. // The need for this should go away once the version of phantomjs gets upgraded to 2.0 'test/polyfills.js', 'src/thirdparty/jquery-2.1.3.min.js', 'src/thirdparty/less.min.js' ], helpers : [ 'test/spec/PhantomHelper.js' ], template : require('grunt-template-jasmine-requirejs'), templateOptions: { requireConfig : { baseUrl: 'src', paths: { 'test' : '../test', 'perf' : '../test/perf', 'spec' : '../test/spec', 'text' : 'thirdparty/text/text', 'i18n' : 'thirdparty/i18n/i18n' } } } } }, 'jasmine_node': { projectRoot: 'src/extensibility/node/spec/' }, eslint: { grunt: '<%= meta.grunt %>', src: '<%= meta.src %>', test: '<%= meta.test %>', options: { quiet: true } }, shell: { repo: grunt.option("shell-repo") || "../brackets-shell", mac: "<%= shell.repo %>/installer/mac/staging/<%= pkg.name %>.app", win: "<%= shell.repo %>/installer/win/staging/<%= pkg.name %>.exe", linux: "<%= shell.repo %>/installer/linux/debian/package-root/opt/brackets/brackets" } }); // task: install grunt.registerTask('install', [ 'write-config:dev', 'less', 'npm-download-default-extensions', 'npm-install-source', 'pack-web-dependencies' ]); // task: test grunt.registerTask('test', ['eslint', 'jasmine', 'nls-check']); // grunt.registerTask('test', ['eslint', 'jasmine', 'jasmine_node', 'nls-check']); // task: set-release // Update version number in package.json and rewrite src/config.json grunt.registerTask('set-release', ['update-release-number', 'write-config:dev']); grunt.registerTask('build-common', [ 'eslint:src', 'jasmine', 'clean', 'less', 'targethtml', 'useminPrepare', 'htmlmin', 'requirejs', 'concat', /*'cssmin',*/ /*'uglify',*/ 'copy:dist', 'npm-install', 'cleanempty', 'usemin', 'build-config', 'clean:node_modules_test_dir' ]); // task: build grunt.registerTask('build', [ 'write-config:dist', 'build-common' ]); // task: build grunt.registerTask('build-prerelease', [ 'write-config:prerelease', 'build-common' ]); // Default task. grunt.registerTask('default', ['test']); }; ================================================ FILE: ISSUE_TEMPLATE.md ================================================ ### Prerequisites * [ ] Can you reproduce the problem with `Debug -> Reload Without Extensions`? * [ ] Did you perform a cursory search to see if your bug or enhancement is already reported? * [ ] Did you read the [Troubleshooting guide](https://github.com/adobe/brackets/wiki/Troubleshooting)? For more information on how to write a good bug report read [here](https://github.com/adobe/brackets/wiki/How-to-Report-an-Issue) For more information on how to contribute read [here](https://github.com/adobe/brackets/blob/master/CONTRIBUTING.md) ### Description [Description of the bug or feature] ### Steps to Reproduce 1. [First Step] 2. [Second Step] 3. [and so on...] **Expected behavior:** [What you expected to happen] **Actual behavior:** [What actually happened] ### Versions Please include the OS and what version of the OS you're running. Please include the version of Brackets. You can find it under `Help -> About Brackets` (Windows and Linux) or `Brackets -> About Brackets` (macOS) ================================================ FILE: LICENSE ================================================ Copyright (c) 2012 - present Adobe Systems Incorporated. All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: NOTICE ================================================ Brackets is licensed under the MIT license (see LICENSE file). Brackets uses the following third party libraries that may have licenses differing from that of Brackets itself. Third Party Software Notices: ============================= "Cowboy" Ben Alman Copyright © 2010-2012 "Cowboy" Ben Alman Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. The Android Open Source Project Copyright (C) 2008 The Android Open Source Project All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. The Android Open Source Project Copyright (C) 2009, 2010 The Android Open Source Project This product includes software licensed under the Apache License, Version 2.0 http://www.apache.org/licenses/LICENSE-2.0. The ANGLE Project Authors Copyright (C) 2002-2010 The ANGLE Project Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of TransGaming Inc., Google Inc., 3DLabs Inc. Ltd., nor the names of their contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. The Apache Software Foundation This product includes software licensed under the Apache License, Version 2.0 http://www.apache.org/licenses/LICENSE-2.0Apple Inc. WebKit is open source software with portions licensed under the LGPL and BSD licenses. Complete license and copyright information can be found within the code at http://opensource.adobe.com/wiki/display/webkit/Webkit. Copyright (C) 2005 Apple Inc. All rights reserved. Copyright (C) 2006 Rob Buis Copyright CNRI, All Rights Reserved. Copyright (C) 2006 Graham Dennis. All rights reserved. Copyright (C) 2007 Robin Dunn. All rights reserved. Copyright (C) 2006 Michael Emmel. All rights reserved. Copyright (C) 2007 Holger Hans Peter Freyther Copyright (C) 2006 Justin Haygood Copyright (C) Hewlett-Packard Company Copyright (C) 1997-2004, International Business Machines Corporation and others. All Rights Reserved. Copyright (C) 2006 Nefaur Khandker. All rights reserved. Copyright (C) 2006 Kimmo Kinnunen Copyright (C) 2005 Ben La Monica. All rights reserved. Copyright (C) 2007 Matt Lilek Copyright (C) 1991, 2000, 2001 by Lucent Technologies Copyright (C) 2006 Dave MacLachlan Copyright (C) 2006, Nokia Corporation. All rights reserved. Copyright (C) 2006, 2007 Vladimir Olexa Copyright (C) 2007 Kevin Ollivier Copyright 2005 Maksim Orlovich Copyright (C) 2007 Alexey Proskuryakov Copyright 2005 Frerich Raabe Copyright (C) 2006 Mark Rowe. All rights reserved. Copyright (C) 2006 Zack Rusin Copyright (C) 2006 Eric Seidel Copyright (C) 2006 David Smith Copyright (C) 2006 James G. Speth Copyright (C) 2006 George Staikos. All rights reserved. Copyright (C) 2007 Staikos Computing Services Inc. Copyright (c) 2000 Malte Starostik Copyright (c) 2006 Thomas Stromberg Copyright (C) 2007 Alp Toker Copyright (C) 2006 Trolltech ASA Copyright (C) 1997-2005 University of Cambridge Copyright 2000 Guido van Rossum. Copyright (C) 2006 Samuel Weinig Copyright (C) 2004 by Michal Zalewski Copyright (C) 2006 Nikolas Zimmermann Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of Apple Inc. ("Apple") nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Apple Inc. Portions licensed from Apple Computer, Inc. under the terms of the Apple Public Source License, Version 2. The source code version of these portions and the license are available at http://www.opensource.apple.com/apsl/. Apple Inc. Copyright (C) 2006, 2007 Apple Computer, Inc. All rights reserved. Copyright (C) 2007, 2008 Apple Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. AUTHORS.txt Copyright (c) 2009, 2010, 2011, 2012 AUTHORS.txt (http://jqueryui.com/about) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Mihai Bazon Copyright 2010 (c) Mihai Bazon Based on parse-js (http://marijn.haverbeke.nl/parse-js/). Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS”AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Kin Blas Copyright (c) 2011, Kin Blas. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Kin Blas nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL KIN BLASBE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Boost Contributors Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Mark Borgerding Copyright (c) 2003-2004, Mark Borgerding. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Ivan Bozhanov Copyright (c) 2012 Ivan Bozhanov Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Brian J. Brennan Copyright (c) 2013 Brian J. Brennan Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute,sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.Victor Carbune Copyright (C) 2012 Victor Carbune (victor@rosedu.org) Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Codility Limited Copyright (c) 2012 Codility Limited, 107 Cheapside, London EC2V 6DN, UK Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Lachie Cox Copyright (c) 2008 Lachie Cox Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Douglas Crockford - JSMin Copyright (c) 2002 Douglas Crockford (www.JSLint.com/www.crockford.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute,sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. The Software shall be used for Good, not Evil. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Theo de Raat Copyright (c) 1992, 1993 Theo de Raadt All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. All advertising materials mentioning features or use of this software must display the following acknowledgement: This product includes software developed by Theo de Raadt. 4. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Debuggable Limited Copyright (c) 2011 Debuggable Limited felix@debuggable.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rightsto use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software isfurnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS ORIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THEAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHERLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS INTHE SOFTWARE. Egor Egorov Egor Egorov, me@egorfine.com. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Copyright 2013-2014 Facebook, Inc. This product includes software licensed under the Apache License, Version 2.0 http://www.apache.org/licenses/LICENSE-2.0. BSD License For Immutable JS software Copyright (c) 2014, Facebook, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name Facebook nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Copyright (c) 2014, Facebook, Inc. All rights reserved. React JavaScript library Licensed under the Apache License, Version 2.0 http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. See also: https://github.com/facebook/react/blob/master/LICENSE Timothy Farrell Copyright (c) 2010 Timothy Farrell Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Nick Galbreath Copyright (c) 2005, 2006 Nick Galbreath -- nickg [at] modp [dot] com All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of modp.com nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. David M. Gay and Lucent Technologies The author of this software is David M. Gay. Copyright (c) 1991, 1996, 2000, 2001 by Lucent Technologies. Permission to use, copy, modify, and distribute this software for any purpose without fee is hereby granted, provided that this entire notice is included in all copies of any software which is or includes a copy or modification of this software and in all copies of the supporting documentation for such software.THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED WARRANTY. IN PARTICULAR, NEITHER THE AUTHOR NOR LUCENT MAKES ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE. Felix Geisendörfer Copyright (C) 2011-2012 Felix Geisendörfer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Daniel Glazman Copyright (C) 2011 by Daniel Glazman Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Google Copyright (c) 2003, 2004, 2005, 2006, 2008, 2009, 2010 Google Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Google, Inc. Copyright 2002, 2007-2008 Google Inc. This product includes software licensed under the Apache License, Version 2.0 http://www.apache.org/licenses/LICENSE-2.0. Google –The Chromium Authors Copyright (c) 2006-2008, 2009, 2010 The Chromium Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Google – the V8 Project Authors Copyright 2009 the V8 project authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributionsin binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Marshal A. Greenblatt and Google Inc. Copyright (c) 2008-2011 Marshall A. Greenblatt. Portions Copyright (c) 2006-2009 Google Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Google Inc. nor the name Chromium Embedded Framework nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Brian Grinstead Copyright (c) 2011, Brian Grinstead, http://briangrinstead.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. James Halliday Copyright 2010 James Halliday (mail@substack.net) This project is free software released under the MIT/X11 license: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rightsto use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software isfurnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS ORIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THEAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHERLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS INTHE SOFTWARE. Eran Hammer Copyright (c) 2012-2013, Eran Hammer. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Eran Hammer nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ANDANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIEDWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE AREDISCLAIMED. IN NO EVENT SHALL ERAN HAMMER BE LIABLE FOR ANYDIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED ANDON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THISSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Marijn Haverbeke (codemirror, tern, acorn) Copyright (C) 2012 by Marijn Haverbeke Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Please note that some subdirectories of the CodeMirror distribution include their own LICENSE files, and are released under different licences. Ariya Hidayat and other contributors Copyright (C) 2012, 2011 Ariya Hidayat and other contributors.Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: •Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. •Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. TJ Holowaychuk Copyright (c) 2010-2012 TJ Holowaychuk Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Independent JPEG GroupThis software is based in part on the work of the Independent JPEG Group. Intel Corporation Copyright © 2008-2010 Intel Corporation Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. International Business Machines Corporation – ICU 1.8.1 and later Copyright (c) 1995-2003, 1995 - 2006, 1995-2007, 1995-2009, International Business Machines Corporation and others All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, provided that the above copyright notice(s) and this permission notice appear in all copies of the Software and that both the above copyright notice(s) and this permission notice appear in supporting documentation. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. Except as contained in this notice, the name of a copyright holder shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Software without prior written authorization of the copyright holder. Peter Johnson Copyright (C) 2001-2007 Peter Johnson Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND OTHER CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR OTHER CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Joyent, Inc. and other Node contributors Copyright Joyent, Inc. and other Node contributors. All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ==== This license applies to all parts of Node that are not externally maintained libraries. The externally maintained libraries used by Node are: - V8, located at deps/v8. V8's license follows: This license applies to all parts of V8 that are not externally maintained libraries. The externally maintained libraries used by V8 are: - PCRE test suite, located in test/mjsunit/third_party/regexp-pcre.js. This is based on the test suite from PCRE- 7.3, which is copyrighted by the University of Cambridge and Google, Inc. The copyright notice and license are embedded in regexp-pcre.js. - Layout tests, located in test/mjsunit/third_party. These are based on layout tests from webkit.org which are copyrighted by Apple Computer, Inc. and released under a 3- clause BSD license. - Strongtalk assembler, the basis of the files assembler-arm-inl.h, assembler-arm.cc, assembler-arm.h, assembler-ia32-inl.h, assembler-ia32.cc, assembler-ia32.h, assemblerx64-inl.h, assembler-x64.cc, assembler-x64.h, assembler-mips-inl.h, assembler-mips.cc, assembler-mips.h, assembler.cc and assembler.h. This code is copyrighted by Sun Microsystems Inc. and released under a 3-clause BSD license. - Valgrind client API header, located at third_party/valgrind/valgrind.h. This is release under the BSD license. These libraries have their own licenses; we recommend you read them, as their terms may differ from the terms below. Copyright 2006-2012, the V8 project authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - libev, located at deps/uv/src/unix/ev. libev's license follows: All files in libev are Copyright (C)2007,2008,2009 Marc Alexander Lehmann. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Alternatively, the contents of this package may be used under the terms of the GNU General Public License ("GPL") version 2 or any later version, in which case the provisions of the GPL are applicable instead of the above. If you wish to allow the use of your version of this package only under the terms of the GPL and not to allow others to use your version of this file under the BSD license, indicate your decision by deleting the provisions above and replace them with the notice and other provisions required by the GPL in this and the other files of this package. If you do not delete the provisions above, a recipient may use your version of this file under either the BSD or the GPL. - libeio, located at deps/uv/src/unix/eio. libeio's license follows: All files in libeio are Copyright (C)2007,2008 Marc Alexander Lehmann. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Alternatively, the contents of this package may be used under the terms of the GNU General Public License ("GPL") version 2 or any later version, in which case the provisions of the GPL are applicable instead of the above. If you wish to allow the use of your version of this package only under the terms of the GPL and not to allow others to use your version of this file under the BSD license, indicate your decision by deleting the provisions above and replace them with the notice and other provisions required by the GPL in this and the other files of this package. If you do not delete the provisions above, a recipient may use your version of this file under either the BSD or the GPL. - WAF build system, located at tools/waf*. WAF's license follows: Copyright Thomas Nagy, 2005-2011 Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -C-Ares, an asynchronous DNS client, located at deps/uv/src/ares. C-Ares license follows: Copyright 1998 by the Massachusetts Institute of Technology. Permission to use, copy, modify, and distribute this software and its documentation for any purpose and without fee is hereby granted, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation, and that the name of M.I.T. not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission.M.I.T. makes no representations about the suitability of this software for any purpose. It is provided "as is" without express or implied warranty. - OpenSSL located at deps/openssl. OpenSSL is cryptographic software written by Eric Young (eay@cryptsoft.com) to provide SSL/TLS encryption. OpenSSL's license follows: =============================================================== Copyright (c) 1998-2011 The OpenSSL Project. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1.Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. All advertising materials mentioning features or use of this software must display the following acknowledgment: "This product includes software developed by the OpenSSL Project for use in the OpenSSL Toolkit. (http://www.openssl.org/)" 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to endorse or promote products derived from this software without prior written permission. For written permission, please contact openssl-core@openssl.org. 5. Products derived from this software may not be called "OpenSSL" nor may "OpenSSL" appear in their names without prior written permission of the OpenSSL Project. 6. Redistributions of any form whatsoever must retain the following acknowledgment: "This product includes software developed by the OpenSSL Project for use in the OpenSSL Toolkit (http://www.openssl.org/)" THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. =============================================================== This product includes cryptographic software written by Eric Young (eay@cryptsoft.com). This product includes software written by Tim Hudson (tjh@cryptsoft.com). - HTTP Parser, located at deps/http_parser. HTTP Parser's license follows: http_parser.c is based on src/http/ngx_http_parse.c from NGINX copyright Igor Sysoev. Additional changes are licensed under the same terms as NGINX and copyright Joyent, Inc. and other Node contributors. All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -Closure Linter is located at tools/closure_linter. Closure's license follows: Copyright (c) 2007, Google Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.* Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - tools/cpplint.py is a C++ linter. Its license follows: Copyright (c) 2009 Google Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - lib/buffer_ieee754.js. Its license follows: Copyright (c) 2008, Fair Oaks Labs, Inc. All rightsreserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Fair Oaks Labs, Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - lib/punycode.js is copyright 2011 Mathias Bynens and released under the MIT license. Punycode.js Copyright 2011 Mathias Bynens Available under MIT license - tools/gyp. GYP is a meta-build system. GYP's license follows: Copyright (c) 2009 Google Inc. All rights reserved.Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - Zlib at deps/zlib. zlib's license follows: zlib.h -- interface of the 'zlib' general purpose compression library version 1.2.4, March 14th, 2010 Copyright (C) 1995-2010 Jean-loup Gailly and Mark Adler This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.3. This notice may not be removed or altered from any source distribution. Jean-loup Gailly Mark Adler - npm is a package manager program located at deps/npm. npm's license follows: Copyright 2009-2012, Isaac Z. Schlueter (the "Original Author") All rights reserved. MIT +no-false-attribs License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. Distributions of all or part of the Software intended to be used by the recipients as they would use the unmodified Software, containing modifications that substantially alter, remove, or disable functionality of the Software, outside of the documented configuration mechanisms provided by the Software, shall be modified such that the Original Author's bug reporting email addresses and urls are either replaced with the contact information of the parties responsible for the changes, or removed entirely. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Except where noted, this license applies to any and all software programs and associated documentation files created by the Original Author, when distributed with the Software. "Node.js" and "node" trademark Joyent, Inc. npm is not officially part of the Node.js project, and is neither owned by nor officially affiliated with Joyent, Inc. Packages published in the npm registry are not part of npm itself, are the sole property of their respective maintainers, and are not covered by this license."npm Logo" created by Mathias Pettersson and Brian Hammond, used with permission. "Gubblebum Blocky" font Copyright (c) 2007 by Tjarda Koster, http://jelloween.deviantart.com included for use in the npm website and documentation, used with permission. This program uses "node-uuid", Copyright (c) 2010 Robert Kieffer, according to the terms of the MIT license. This program uses "request", Copyright (c) 2011 Mikeal Rogers, according to the terms of the Apache license. This program uses "mkdirp", Copyright (c) 2010 James Halliday, according to the terms of the MIT/X11 license. - tools/doc/node_modules/marked. Marked is a Markdown parser. Marked's license follows: Copyright (c) 2011-2012, Christopher Jeffrey (https://github.com/chjj/) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - test/gc/node_modules/weak. Node-weak is a node.js addon that provides garbage collector notifications. Node-weak's license follows: Copyright (c) 2011, Ben Noordhuis Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -src/ngx-queue.h. ngx-queue.h is taken from the nginx source tree. nginx's license follows: Copyright (C) 2002-2012 Igor Sysoev Copyright (C) 2011,2012 Nginx, Inc. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, ORCONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Jussi Kalliokoski Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Stanislav Karchebny Copyright (c) 2001 Stanislav Karchebny Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND OTHER CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR OTHER CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Robert Kieffer Copyright (c) 2010-2012 Robert Kieffer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Learnboost Copyright (c) 2012 LearnBoost Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. The LevelDB Authors Copyright (c) 2011 The LevelDB Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.* Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. The Libjingle Authors Copyright 2012, The Libjingle Authors. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. The LibYuv Project Authors Copyright 2011 The LibYuv Project Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Google nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Linux Foundation, IBM Corp., and Sun Microsystems, Inc. Copyright (c) Linux Foundation 2007, 2008 Copyright (c) IBM Corp. 2006 Copyright (c) Sun Microsystems, Inc. 2000, 2006 --- Copyright (c) 2007, 2010 Linux Foundation Copyright (c) 2006 IBM Corporation Copyright (c) 2000, 2006 Sun Microsystems, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the Linux Foundation nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. MarkLogic Corporation Copyright (C) 2011 by MarkLogic Corporation Author: Mike Brevoort Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rightsto use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software isfurnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS ORIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHERLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Nick Mathewson, Dug Song and The Regents of the University of California Copyright (c) 2005 Nick Mathewson Copyright (c) 2000 Dug Song Copyright (c) 1993 The Regents of the University of California. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.3. Neither the name of the University nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Charlie McConnell Copyright (C) 2011 by Charlie McConnell Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Caolan McMahon Copyright (c) 2010 Caolan McMahon Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rightsto use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software isfurnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS ORIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THEAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHERLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS INTHE SOFTWARE. Microsoft Corporation Portions of this software are licensed under the Common Public License 1.0 (http://opensource.org/licenses/cpl.php). The source code for these files, along with their modifications can be found at: https://github.com/adobe/bracketsshell/tree/master/installer/win. Todd C. Miller Copyright (c) 1996, 1998-2002 Todd C. Miller All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions ofsource code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. 4. Products derived from this software may not be called "Sudo" nor may "Sudo" appear in their names without specific prior written permission from the author. THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Mozilla Foundation and Marti Maria Copyright (C) 2009 Mozilla Foundation Copyright (C) 1998-2007 Marti Maria Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Mozilla.org Portions licensed under the Mozilla Public License Version 1.1, available at www.mozilla.org. Near Infinity Corporation Copyright (c) 2012 Near Infinity Corporation Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OFMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE ANDNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BELIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTIONOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTIONWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Netscape Communications Portions licensed under the Netscape Public License Version 1.1, available at www.mozilla.org/NPL/. Mark Olesen, Queen’s Univ at Kingston Copyright(c)1995,97 Mark Olesen Queen's Univ at Kingston (Canada) Permission to use, copy, modify, and distribute this software for any purpose without fee is hereby granted, provided that this entire notice is included in all copies of any software which is or includes a copy or modification of this software and in all copies of the supporting documentation for such software. THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED WARRANTY. IN PARTICULAR, NEITHER THE AUTHOR NOR QUEEN'S UNIVERSITY AT KINGSTON MAKES ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE. All of which is to say that you can do what you like with this source code provided you don't try to sell it as your own and you include an unaltered copy of this message (including the copyright). It is also implicitly understood that bug fixes and improvements should make their way back to the general Internet community so that everyone benefits. Sascha Peilicke Copyright (c) 2011, Sascha Peilicke Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Lakshan Perera Copyright (c) 2011 Lakshan Perera (laktek.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Jeff Pickhardt Copyright (c) 2011 Jeff Pickhardt Modified from the Python CodeMirror mode, Copyright (c) 2010 Timothy Farrell Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rightsto use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software isfurnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS ORIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHERLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Pivotal Labs Copyright (c) 2008-2010 Pivotal Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Niels Provos Copyright (c) 2000-2007 Niels Provos All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission.THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Bjorn Reese and Daniel Stenberg Copyright (C) 2001 Bjorn Reese and Daniel Stenberg. Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE AUTHORS AND CONTRIBUTORS ACCEPT NO RESPONSIBILITY IN ANY CONCEIVABLE MANNER. The Regents of the University of California Copyright (c) 1982, 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994 The Regents of the University of California. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. All advertising materials mentioning features or use of this software must display the following acknowledgement: This product includes software developed by the University of California, Berkeley and its contributors.4. Neither the name of the University nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Jonathan 'Wolf' Rentzsch Copyright (c) 2003-2009 Jonathan 'Wolf' Rentzsch: Some rights reserved: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. John Resig and The Dojo Foundation Copyright (c) 2009,2011 John Resig Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Includes Sizzle.js http://sizzlejs.com/ Copyright 2011, The Dojo Foundation Released under the MIT, BSD, and GPL Licenses. JP Richardson Copyright (c) 2011-2012 JP Richardson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Henryk Richter Copyright (C) 2007 Henryk Richter. built upon xdf objfmt (C) Peter Johnson Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND OTHER CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR OTHER CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. RSA Security, Inc. Portions licensed under the Mozilla Public License Version 1.1, available at www.mozilla.org. Sabaca Copyright (C) 2011 by Sabaca under the MIT license. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rightsto use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software isfurnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS ORIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.Isaac Z. Schlueter Copyright 2009, 2010, 2011 Isaac Z. Schlueter. All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIESOF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE ANDNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHTHOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISINGFROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OROTHER DEALINGS IN THE SOFTWARE. Isaac Z. Schlueter Copyright (c) Isaac Z. Schlueter ("Author") All rights reserved. The BSD License Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' ANDANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THEIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULARPURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORSBE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, ORCONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OFSUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; ORBUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCEOR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVENIF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Thomas Schmid Copyright (C) 2012 Thomas Schmid Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Sencha Inc., LearnBoost, TJ Holowaychuk Copyright (c) 2010 Sencha Inc. Copyright (c) 2011 LearnBoost Copyright (c) 2011 TJ Holowaychuk Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Julian Seward http://www.bzip.org Version 1.0.3 of 15 February 2005 Copyright © 1996-2005 Julian Seward This program, bzip2, the associated library libbzip2, and all documentation, are copyright © 1996-2005 Julian Seward. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. PATENTS: To the best of my knowledge, bzip2 and libbzip2 do not use any patented algorithms. However, I do not have the resources to carry out a patent search. Therefore I cannot give any guarantee of the above statement. Julian R. Seward (Valgrind.h) This file is part of Valgrind, a dynamic binary instrumentation framework. Copyright (C) 2000-2008 Julian Seward. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 3. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 4. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, ORTORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Roman Shtylman Copyright (C) Roman Shtylman Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Silicon Graphics, Inc. The Original Code is: OpenGL Sample Implementation, Version 1.2.1, released January 26, 2000, developed by Silicon Graphics, Inc. The Original Code is Copyright (c) 1991- 2000 Silicon Graphics, Inc. Copyright in any portions created by third parties is as indicated elsewhere herein. All Rights Reserved. Skype Limited Copyright (c) 2006-2011, Skype Limited. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: -Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. -Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - Neither the name of Internet Society, IETF or IETF Trust, nor the names of specific contributors, may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. sourceLair Copyright (c) 2011 sourceLair Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Einar Otto Stangvik Copyright (c) 2011-2012 Einar Otto Stangvik Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Suitable Systems Copyright (c) 2010 Suitable Systems All rights reserved. Developed by: Daniel Griscom Suitable Systemshttp://www.suitable.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal with the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimers. -Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimers in the documentation and/or other materials provided with the distribution. - Neither the names of Suitable Systems nor the names of its contributors may be used to endorse or promote products derived from this Software without specific prior written permission. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE SOFTWARE. For more information about SMSLib, see or contact Daniel Griscom Suitable Systems 1 Centre Street, Suite 204 Wakefield, MA 01880 (781) 665-0053 Alexei Svitkine Copyright (C) 2010 Alexei Svitkine Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the author nor the names of other contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND OTHER CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR OTHER CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Simon Tatham and Julian Hall The Netwide Assembler is copyright (C) 1996 Simon Tatham and Julian Hall. All rights reserved. The software is redistributable under the license given in the file "License" distributed in the NASM archive. Copyright 1996-2009 the NASM Authors - All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Benjamin Thomas, Robert Kieffer Copyright (c) 2010 Benjamin Thomas, Robert Kieffer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Twitter, Inc. This product includes software licensed under the Apache License, Version 2.0 http://www.apache.org/licenses/LICENSE-2.0 Ubalo, Inc. Copyright (c) 2011, Ubalo, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the Ubalo, Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL UBALO, INC BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Michael Urman Copyright (C) 2003-2007 Michael Urman Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND OTHER CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR OTHER CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Jean-Marc Valin/Xiph.org Foundation © 2002-2003, Jean-Marc Valin/Xiph.Org Foundation Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of the Xiph.org Foundation nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. This software is provided by the copyright holders and contributors “as is” and any express or implied warranties, including, but not limited to, the implied warranties of merchantability and fitness for a particular purpose are disclaimed. In no event shall the foundation or contributors be liable for any direct, indirect, incidental, special, exemplary, or consequential damages (including, but not limited to, procurement of substitute goods or services; loss of use, data, or profits; or business interruption) however caused and on any theory of liability, whether in contract, strict liability, or tort (including negligence or otherwise) arising in any way out of the use of this software, even if advised of the possibility of such damage. Walmart Copyright (c) 2011-2013, Walmart. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Walmart nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ANDANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIEDWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE AREDISCLAIMED. IN NO EVENT SHALL WALMART BE LIABLE FOR ANYDIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED ANDON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THISSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Chris Wanstrath and Jan Lehnardt (Mustache.js) Copyright (c) 2009 Chris Wanstrath (Ruby) Copyright (c) 2010 Jan Lehnardt (JavaScript) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. The WebM Project authors Copyright (c) 2010, The WebM Project authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Google, nor the WebM Project, nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. The WebRTC project authors Copyright (c) 2011, The WebRTC project authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Google nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Additional IP Rights Grant (Patents) "This implementation" means the copyrightable works distributed by Google as part of the WebRTC code package.Google hereby grants to you a perpetual, worldwide, non-exclusive, no-charge, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, transfer, and otherwise run, modify and propagate the contents of this implementation of the WebRTC code package, where such license applies only to those patent claims, both currently owned by Google and acquired in the future, licensable by Google that are necessarily infringed by this implementation of the WebRTC code package. This grant does not include claims that would be infringed only as a consequence of further modification of this implementation. If you or your agent or exclusive licensee institute or order or agree to the institution of patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that this implementation of the WebRTC code package or any code incorporated within this implementation of the WebRTC code package constitutes direct or contributory patent infringement, or inducement of patent infringement, then any patent rights granted to you under this License for this implementation of the WebRTC code package shall terminate as of the date such litigation is filed. Maxim Yegorushkin Copyright (c) 2006 Maxim Yegorushkin All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. AppendTo LLC Copyright 2013 appendTo LLC. (http://appendto.com/team) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Chris Winberry Copyright 2010, 2011, Chris Winberry . All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Peter Zotov , Public Domain Notice (murmurhash3-js) The MurmurHash3 algorithm was created by Austin Appleby. This JavaScript port was authored * by Peter Zotov (based on Java port by Yonik Seeley) and is placed into the public domain. * The author hereby disclaims copyright to this source code. node-methods is authored by TJ Holowaychuck under the terms of the MIT license. Felix Geisendörfer Copyright (C) 2011 Felix Geisendörfer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Andrew Kelley Copyright (c) 2013 Andrew Kelley Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Marco Aurelio Copyright (c) 2013 Marco Aurelio Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Lea Verou Copyright (c) 2013 Lea Verou. All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. The Dojo Foundation Copyright 2012-2013 The Dojo Foundation Based on Underscore.js 1.5.2, copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Isaac Z. Schlueter Copyright 2009, 2010, 2011 Isaac Z. Schlueter. All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. EditorConfig Team Copyright © 2012 EditorConfig Team Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Copyright (C) 2010-2013 Philipp Dunkel Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ | :warning: On September 1, 2021, Adobe will end support for Brackets. If you would like to continue using, maintaining, and improving Brackets, you may fork the project on [GitHub](https://github.com/adobe/brackets). Through Adobe’s partnership with Microsoft, we encourage users to migrate to [Visual Studio Code](https://aka.ms/brackets-to-vscode), Microsoft’s free code editor built on open source. | --- Welcome to Brackets! [![Build Status](https://travis-ci.org/adobe/brackets.svg?branch=master)](https://travis-ci.org/adobe/brackets) ------------------- Brackets is a modern open-source code editor for HTML, CSS and JavaScript that's *built* in HTML, CSS and JavaScript. What makes Brackets different from other web code editors? * **Tools shouldn't get in your way.** Instead of cluttering up your coding environment with lots of panels and icons, the Quick Edit UI in Brackets puts context-specific code and tools inline. * **Brackets is in sync with your browser.** With Live Preview, Brackets works directly with your browser to push code edits instantly and jump back and forth between your real source code and the browser view. * **Do it yourself.** Because Brackets is open source, and built with HTML, CSS and JavaScript, you can [help build](https://github.com/adobe/brackets/blob/master/CONTRIBUTING.md) the best code editor for the web. Brackets may have reached version 1, but we're not stopping there. We have many feature ideas on our [trello board](http://bit.ly/BracketsTrelloBoard) that we're anxious to add and other innovative web development workflows that we're planning to build into Brackets. So take Brackets out for a spin and let us know how we can make it your favorite editor. You can see some [screenshots of Brackets](https://github.com/adobe/brackets/wiki/Brackets-Screenshots) on the wiki, [intro videos](http://www.youtube.com/user/CodeBrackets) on YouTube, and news on the [Brackets blog](http://blog.brackets.io/). How to install and run Brackets ------------------------------- #### Download Installers for the latest stable build for Mac, Windows and Linux (Debian/Ubuntu) can be [downloaded here](http://brackets.io/). #### Usage By default, Brackets opens a folder containing some simple "Getting Started" content. You can choose a different folder to edit using *File > Open Folder*. Most of Brackets should be pretty self-explanatory, but for information on how to use its unique features, like Quick Edit and Live Preview, please read [How to Use Brackets](http://github.com/adobe/brackets/wiki/How-to-Use-Brackets). Also, see the [release notes](http://github.com/adobe/brackets/wiki/Release-Notes) for a list of new features and known issues in each build. In addition to the core features built into Brackets, there is a large and growing community of developers building extensions that add all sorts of useful functionality. See the [Brackets Extension Registry](https://registry.brackets.io/) for a list of available extensions. For installation instructions, see the [extensions wiki page](https://github.com/adobe/brackets/wiki/Brackets-Extensions). #### Need help? Having problems starting Brackets the first time, or not sure how to use Brackets? Please review [Troubleshooting](https://github.com/adobe/brackets/wiki/Troubleshooting), which helps you to fix common problems and find extra help if needed. Helping Brackets ---------------- #### I found a bug! If you found a repeatable bug, and [troubleshooting](https://github.com/adobe/brackets/wiki/Troubleshooting) tips didn't help, then be sure to [search existing issues](https://github.com/adobe/brackets/issues) first. Include steps to consistently reproduce the problem, actual vs. expected results, screenshots, and your OS and Brackets version number. Disable all extensions to verify the issue is a core Brackets bug. [Read more guidelines for filing good bugs.](https://github.com/adobe/brackets/wiki/How-to-Report-an-Issue) #### I have a new suggestion, but don't know how to program! For feature requests please first check our [Trello board](http://bit.ly/BracketsBacklog) to see if it's already there; you can upvote it if so. If not, feel free to file it as an issue as above; we'll move it to the feature backlog for you. #### I want to help with the code! Awesome! _There are lots of ways you can help._ First read [CONTRIBUTING.md](https://github.com/adobe/brackets/blob/master/CONTRIBUTING.md), then learn how to [pull the repo and hack on Brackets](https://github.com/adobe/brackets/wiki/How-to-Hack-on-Brackets). The text editor inside Brackets is based on [CodeMirror](http://github.com/codemirror/CodeMirror)—thanks to Marijn for taking our pull requests, implementing feature requests and fixing bugs! See [Notes on CodeMirror](https://github.com/adobe/brackets/wiki/Notes-on-CodeMirror) for info on how we're using CodeMirror. Although Brackets is built in HTML/CSS/JS, it currently runs as a desktop application in a thin native shell, so that it can access your local files. (If you just try to open the index.html file in a browser, it won't work yet.) The native shell for Brackets lives in a separate repo, [adobe/brackets-shell](https://github.com/adobe/brackets-shell/). I want to keep track of how Brackets is doing! ---------------------------------------------- Not sure you needed the exclamation point there, but we like your enthusiasm. #### What's Brackets working on next? * In our [feature backlog](http://bit.ly/BracketsBacklog), the columns to the right (starting from "Development") list the features that we're currently working on. "Ready" shows what we'll be working on next. * Watch our [GitHub activity stream](https://github.com/adobe/brackets/pulse). * Watch our [Waffle Kanban board](https://waffle.io/adobe/brackets): Work items in [![Stories in Ready](https://badge.waffle.io/adobe/brackets.svg?label=ready&title=Ready)](http://waffle.io/adobe/brackets) are next. The entire development process is outlined in the [Developer Guide](https://github.com/adobe/brackets/wiki/Brackets-Developers-Guide). #### Contact info * **E-mail:** [admin@brackets.io](mailto:admin@brackets.io) * **Slack:** [Brackets on Slack](https://brackets.slack.com) (You can join by sending a mail to [admin@brackets.io](mailto:admin@brackets.io) with the subject line `slack registration request` specifying the email addresses you would like to register). * **Developers mailing list:** http://groups.google.com/group/brackets-dev * **Twitter:** [@brackets](https://twitter.com/brackets) * **Blog:** http://blog.brackets.io/ * **IRC:** [#brackets on freenode](http://webchat.freenode.net/?channels=brackets) --- Please note that this project is released with a [Contributor Code of Conduct](https://github.com/adobe/brackets/blob/master/CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms. ================================================ FILE: build.json ================================================ { "version": "release-1.11-prerelease-2", "title" : "Brackets 1.11 2nd Pre-release for community testing", "description" : "This is Brackets 1.11 2nd pre-release build. In this release, CEF has been upgraded for Linux which resolved the much reported libgcrypt dependency, basic es6 support has been added by providing eslint out of the box and making required changes to list Classes/methods/arrow function expressions/generator functions/async functions/class getter-setter methods and much more. ", "prerelease": true } ================================================ FILE: npm-shrinkwrap.json ================================================ { "name": "Brackets", "version": "1.11.0-0", "dependencies": { "abbrev": { "version": "1.1.0", "from": "abbrev@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.0.tgz" }, "acorn": { "version": "5.1.1", "from": "acorn@>=5.0.1 <6.0.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.1.1.tgz", "dev": true }, "acorn-dynamic-import": { "version": "2.0.2", "from": "acorn-dynamic-import@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-2.0.2.tgz", "dev": true, "dependencies": { "acorn": { "version": "4.0.13", "from": "acorn@>=4.0.3 <5.0.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", "dev": true } } }, "acorn-jsx": { "version": "3.0.1", "from": "acorn-jsx@>=3.0.0 <4.0.0", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", "dev": true, "dependencies": { "acorn": { "version": "3.3.0", "from": "acorn@>=3.0.4 <4.0.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", "dev": true } } }, "adm-zip": { "version": "0.4.4", "from": "adm-zip@0.4.4", "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.4.tgz", "dev": true }, "ajv": { "version": "4.11.8", "from": "ajv@>=4.7.0 <5.0.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", "dev": true }, "ajv-keywords": { "version": "1.5.1", "from": "ajv-keywords@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-1.5.1.tgz", "dev": true }, "align-text": { "version": "0.1.4", "from": "align-text@>=0.1.3 <0.2.0", "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", "dev": true }, "amdefine": { "version": "1.0.1", "from": "amdefine@>=0.0.4", "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", "dev": true }, "ansi-escapes": { "version": "1.4.0", "from": "ansi-escapes@>=1.1.0 <2.0.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", "dev": true }, "ansi-regex": { "version": "2.1.1", "from": "ansi-regex@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz" }, "ansi-styles": { "version": "2.2.1", "from": "ansi-styles@>=2.2.1 <3.0.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz" }, "anymatch": { "version": "1.3.0", "from": "anymatch@1.3.0", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.0.tgz" }, "argparse": { "version": "0.1.16", "from": "argparse@>=0.1.11 <0.2.0", "resolved": "https://registry.npmjs.org/argparse/-/argparse-0.1.16.tgz", "dev": true, "dependencies": { "underscore.string": { "version": "2.4.0", "from": "underscore.string@>=2.4.0 <2.5.0", "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.4.0.tgz", "dev": true } } }, "arr-diff": { "version": "2.0.0", "from": "arr-diff@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz" }, "arr-flatten": { "version": "1.1.0", "from": "arr-flatten@>=1.0.1 <2.0.0", "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz" }, "array-differ": { "version": "1.0.0", "from": "array-differ@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", "dev": true }, "array-union": { "version": "1.0.2", "from": "array-union@>=1.0.1 <2.0.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", "dev": true }, "array-uniq": { "version": "1.0.3", "from": "array-uniq@>=1.0.1 <2.0.0", "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", "dev": true }, "array-unique": { "version": "0.2.1", "from": "array-unique@>=0.2.1 <0.3.0", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz" }, "arrify": { "version": "1.0.1", "from": "arrify@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz" }, "asap": { "version": "2.0.6", "from": "asap@>=2.0.3 <2.1.0", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", "dev": true, "optional": true }, "asn1": { "version": "0.2.3", "from": "asn1@>=0.2.3 <0.3.0", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz" }, "asn1.js": { "version": "4.9.1", "from": "asn1.js@>=4.0.0 <5.0.0", "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.9.1.tgz", "dev": true }, "assert": { "version": "1.4.1", "from": "assert@>=1.1.1 <2.0.0", "resolved": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz", "dev": true }, "assert-plus": { "version": "0.2.0", "from": "assert-plus@>=0.2.0 <0.3.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz" }, "async": { "version": "2.1.4", "from": "async@2.1.4", "resolved": "https://registry.npmjs.org/async/-/async-2.1.4.tgz" }, "async-each": { "version": "1.0.1", "from": "async-each@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz" }, "asynckit": { "version": "0.4.0", "from": "asynckit@>=0.4.0 <0.5.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" }, "aws-sign2": { "version": "0.6.0", "from": "aws-sign2@>=0.6.0 <0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz" }, "aws4": { "version": "1.6.0", "from": "aws4@>=1.2.1 <2.0.0", "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz" }, "babel-code-frame": { "version": "6.22.0", "from": "babel-code-frame@>=6.16.0 <7.0.0", "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.22.0.tgz", "dev": true }, "balanced-match": { "version": "1.0.0", "from": "balanced-match@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz" }, "base64-js": { "version": "1.2.1", "from": "base64-js@>=1.0.2 <2.0.0", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.1.tgz", "dev": true }, "bcrypt-pbkdf": { "version": "1.0.1", "from": "bcrypt-pbkdf@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", "optional": true }, "big.js": { "version": "3.1.3", "from": "big.js@>=3.1.3 <4.0.0", "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.1.3.tgz", "dev": true }, "binary": { "version": "0.3.0", "from": "binary@>=0.3.0 <0.4.0", "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz" }, "binary-extensions": { "version": "1.9.0", "from": "binary-extensions@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.9.0.tgz" }, "bl": { "version": "0.9.5", "from": "bl@>=0.9.0 <0.10.0", "resolved": "https://registry.npmjs.org/bl/-/bl-0.9.5.tgz", "dev": true, "dependencies": { "isarray": { "version": "0.0.1", "from": "isarray@0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", "dev": true }, "readable-stream": { "version": "1.0.34", "from": "readable-stream@>=1.0.26 <1.1.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "dev": true }, "string_decoder": { "version": "0.10.31", "from": "string_decoder@>=0.10.0 <0.11.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", "dev": true } } }, "block-stream": { "version": "0.0.9", "from": "block-stream@*", "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", "dev": true }, "bn.js": { "version": "4.11.7", "from": "bn.js@>=4.1.1 <5.0.0", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.7.tgz", "dev": true }, "body-parser": { "version": "1.14.2", "from": "body-parser@>=1.14.0 <1.15.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.14.2.tgz", "dev": true, "dependencies": { "iconv-lite": { "version": "0.4.13", "from": "iconv-lite@0.4.13", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz", "dev": true }, "qs": { "version": "5.2.0", "from": "qs@5.2.0", "resolved": "https://registry.npmjs.org/qs/-/qs-5.2.0.tgz", "dev": true } } }, "boom": { "version": "2.10.1", "from": "boom@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz" }, "brace-expansion": { "version": "1.1.8", "from": "brace-expansion@>=1.1.7 <2.0.0", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz" }, "braces": { "version": "1.8.5", "from": "braces@>=1.8.2 <2.0.0", "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz" }, "brorand": { "version": "1.1.0", "from": "brorand@>=1.0.1 <2.0.0", "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", "dev": true }, "browserify-aes": { "version": "1.0.6", "from": "browserify-aes@>=1.0.4 <2.0.0", "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.0.6.tgz", "dev": true }, "browserify-cipher": { "version": "1.0.0", "from": "browserify-cipher@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.0.tgz", "dev": true }, "browserify-des": { "version": "1.0.0", "from": "browserify-des@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.0.tgz", "dev": true }, "browserify-rsa": { "version": "4.0.1", "from": "browserify-rsa@>=4.0.0 <5.0.0", "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", "dev": true }, "browserify-sign": { "version": "4.0.4", "from": "browserify-sign@>=4.0.0 <5.0.0", "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", "dev": true }, "browserify-zlib": { "version": "0.1.4", "from": "browserify-zlib@>=0.1.4 <0.2.0", "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.1.4.tgz", "dev": true }, "buffer": { "version": "4.9.1", "from": "buffer@>=4.3.0 <5.0.0", "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", "dev": true }, "buffer-shims": { "version": "1.0.0", "from": "buffer-shims@>=1.0.0 <1.1.0", "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz" }, "buffer-xor": { "version": "1.0.3", "from": "buffer-xor@>=1.0.2 <2.0.0", "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", "dev": true }, "buffers": { "version": "0.1.1", "from": "buffers@>=0.1.1 <0.2.0", "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz" }, "builtin-modules": { "version": "1.1.1", "from": "builtin-modules@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", "dev": true }, "builtin-status-codes": { "version": "3.0.0", "from": "builtin-status-codes@>=3.0.0 <4.0.0", "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", "dev": true }, "bytes": { "version": "2.2.0", "from": "bytes@2.2.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.2.0.tgz", "dev": true }, "caller-path": { "version": "0.1.0", "from": "caller-path@>=0.1.0 <0.2.0", "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", "dev": true }, "callsites": { "version": "0.2.0", "from": "callsites@>=0.2.0 <0.3.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", "dev": true }, "camelcase": { "version": "1.2.1", "from": "camelcase@>=1.0.2 <2.0.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", "dev": true }, "caseless": { "version": "0.11.0", "from": "caseless@>=0.11.0 <0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz" }, "center-align": { "version": "0.1.3", "from": "center-align@>=0.1.1 <0.2.0", "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", "dev": true }, "chainsaw": { "version": "0.1.0", "from": "chainsaw@>=0.1.0 <0.2.0", "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz" }, "chalk": { "version": "1.1.3", "from": "chalk@>=1.1.1 <2.0.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz" }, "chokidar": { "version": "1.6.1", "from": "chokidar@1.6.1", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.6.1.tgz" }, "ci-info": { "version": "1.0.0", "from": "ci-info@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.0.0.tgz", "dev": true }, "cipher-base": { "version": "1.0.4", "from": "cipher-base@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", "dev": true }, "circular-json": { "version": "0.3.3", "from": "circular-json@>=0.3.1 <0.4.0", "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", "dev": true }, "clean-css": { "version": "1.0.12", "from": "clean-css@>=1.0.0 <1.1.0", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-1.0.12.tgz", "dev": true, "dependencies": { "commander": { "version": "1.3.2", "from": "commander@>=1.3.0 <1.4.0", "resolved": "https://registry.npmjs.org/commander/-/commander-1.3.2.tgz", "dev": true } } }, "cli-cursor": { "version": "1.0.2", "from": "cli-cursor@>=1.0.1 <2.0.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", "dev": true }, "cli-width": { "version": "2.1.0", "from": "cli-width@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.1.0.tgz", "dev": true }, "cliui": { "version": "2.1.0", "from": "cliui@>=2.1.0 <3.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", "dev": true, "dependencies": { "wordwrap": { "version": "0.0.2", "from": "wordwrap@0.0.2", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", "dev": true } } }, "co": { "version": "4.6.0", "from": "co@>=4.6.0 <5.0.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", "dev": true }, "code-point-at": { "version": "1.1.0", "from": "code-point-at@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", "dev": true }, "coffee-script": { "version": "1.3.3", "from": "coffee-script@>=1.3.3 <1.4.0", "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.3.3.tgz", "dev": true }, "colors": { "version": "0.6.2", "from": "colors@>=0.6.2 <0.7.0", "resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz", "dev": true }, "combined-stream": { "version": "1.0.5", "from": "combined-stream@>=1.0.5 <1.1.0", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz" }, "commander": { "version": "2.11.0", "from": "commander@>=2.9.0 <3.0.0", "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz" }, "concat-map": { "version": "0.0.1", "from": "concat-map@0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" }, "concat-stream": { "version": "1.6.0", "from": "concat-stream@>=1.5.2 <2.0.0", "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz", "dev": true }, "config-chain": { "version": "1.1.11", "from": "config-chain@>=1.1.8 <1.2.0", "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.11.tgz", "dev": true }, "console-browserify": { "version": "1.1.0", "from": "console-browserify@>=1.1.0 <2.0.0", "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", "dev": true }, "constants-browserify": { "version": "1.0.0", "from": "constants-browserify@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", "dev": true }, "content-type": { "version": "1.0.2", "from": "content-type@>=1.0.1 <1.1.0", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.2.tgz", "dev": true }, "core-util-is": { "version": "1.0.2", "from": "core-util-is@>=1.0.0 <1.1.0", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz" }, "create-ecdh": { "version": "4.0.0", "from": "create-ecdh@>=4.0.0 <5.0.0", "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.0.tgz", "dev": true }, "create-hash": { "version": "1.1.3", "from": "create-hash@>=1.1.0 <2.0.0", "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.3.tgz", "dev": true }, "create-hmac": { "version": "1.1.6", "from": "create-hmac@>=1.1.0 <2.0.0", "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.6.tgz", "dev": true }, "cryptiles": { "version": "2.0.5", "from": "cryptiles@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz" }, "crypto-browserify": { "version": "3.11.1", "from": "crypto-browserify@>=3.11.0 <4.0.0", "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.11.1.tgz", "dev": true }, "ctype": { "version": "0.5.3", "from": "ctype@0.5.3", "resolved": "https://registry.npmjs.org/ctype/-/ctype-0.5.3.tgz", "dev": true, "optional": true }, "d": { "version": "1.0.0", "from": "d@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", "dev": true }, "dashdash": { "version": "1.14.1", "from": "dashdash@>=1.12.0 <2.0.0", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "dependencies": { "assert-plus": { "version": "1.0.0", "from": "assert-plus@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" } } }, "date-now": { "version": "0.1.4", "from": "date-now@>=0.1.4 <0.2.0", "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", "dev": true }, "dateformat": { "version": "1.0.2-1.2.3", "from": "dateformat@1.0.2-1.2.3", "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.2-1.2.3.tgz", "dev": true }, "debug": { "version": "2.2.0", "from": "debug@>=2.2.0 <2.3.0", "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", "dev": true }, "decamelize": { "version": "1.2.0", "from": "decamelize@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", "dev": true }, "decompress-zip": { "version": "0.3.0", "from": "decompress-zip@0.3.0", "resolved": "https://registry.npmjs.org/decompress-zip/-/decompress-zip-0.3.0.tgz", "dependencies": { "isarray": { "version": "0.0.1", "from": "isarray@0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" }, "q": { "version": "1.5.0", "from": "q@>=1.1.2 <2.0.0", "resolved": "https://registry.npmjs.org/q/-/q-1.5.0.tgz" }, "readable-stream": { "version": "1.1.14", "from": "readable-stream@>=1.1.8 <2.0.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz" }, "string_decoder": { "version": "0.10.31", "from": "string_decoder@>=0.10.0 <0.11.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz" } } }, "deep-is": { "version": "0.1.3", "from": "deep-is@>=0.1.3 <0.2.0", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", "dev": true }, "del": { "version": "2.2.2", "from": "del@>=2.0.2 <3.0.0", "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", "dev": true }, "delayed-stream": { "version": "1.0.0", "from": "delayed-stream@>=1.0.0 <1.1.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" }, "depd": { "version": "1.1.1", "from": "depd@>=1.1.0 <1.2.0", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", "dev": true }, "des.js": { "version": "1.0.0", "from": "des.js@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", "dev": true }, "diffie-hellman": { "version": "5.0.2", "from": "diffie-hellman@>=5.0.0 <6.0.0", "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.2.tgz", "dev": true }, "doctrine": { "version": "2.0.0", "from": "doctrine@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.0.0.tgz", "dev": true }, "dom-walk": { "version": "0.1.1", "from": "dom-walk@>=0.1.0 <0.2.0", "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.1.tgz", "dev": true }, "domain-browser": { "version": "1.1.7", "from": "domain-browser@>=1.1.1 <2.0.0", "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.1.7.tgz", "dev": true }, "ecc-jsbn": { "version": "0.1.1", "from": "ecc-jsbn@>=0.1.1 <0.2.0", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", "optional": true }, "ee-first": { "version": "1.1.1", "from": "ee-first@1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "dev": true }, "elliptic": { "version": "6.4.0", "from": "elliptic@>=6.0.0 <7.0.0", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.0.tgz", "dev": true }, "emojis-list": { "version": "2.1.0", "from": "emojis-list@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", "dev": true }, "enhanced-resolve": { "version": "3.4.1", "from": "enhanced-resolve@>=3.0.0 <4.0.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-3.4.1.tgz", "dev": true }, "errno": { "version": "0.1.4", "from": "errno@>=0.1.1 <0.2.0", "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.4.tgz", "dev": true }, "error-ex": { "version": "1.3.1", "from": "error-ex@>=1.2.0 <2.0.0", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", "dev": true }, "es5-ext": { "version": "0.10.24", "from": "es5-ext@>=0.10.14 <0.11.0", "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.24.tgz", "dev": true }, "es6-iterator": { "version": "2.0.1", "from": "es6-iterator@>=2.0.1 <2.1.0", "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.1.tgz", "dev": true }, "es6-map": { "version": "0.1.5", "from": "es6-map@>=0.1.3 <0.2.0", "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz", "dev": true }, "es6-set": { "version": "0.1.5", "from": "es6-set@>=0.1.5 <0.2.0", "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", "dev": true }, "es6-symbol": { "version": "3.1.1", "from": "es6-symbol@>=3.1.1 <3.2.0", "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", "dev": true }, "es6-weak-map": { "version": "2.0.2", "from": "es6-weak-map@>=2.0.1 <3.0.0", "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz", "dev": true }, "escape-string-regexp": { "version": "1.0.5", "from": "escape-string-regexp@>=1.0.2 <2.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" }, "escope": { "version": "3.6.0", "from": "escope@>=3.6.0 <4.0.0", "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz", "dev": true }, "eslint": { "version": "3.19.0", "from": "eslint@>=3.0.0 <4.0.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-3.19.0.tgz", "dev": true, "dependencies": { "argparse": { "version": "1.0.9", "from": "argparse@>=1.0.7 <2.0.0", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz", "dev": true }, "esprima": { "version": "4.0.0", "from": "esprima@>=4.0.0 <5.0.0", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", "dev": true }, "js-yaml": { "version": "3.9.1", "from": "js-yaml@>=3.5.1 <4.0.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.9.1.tgz", "dev": true } } }, "espree": { "version": "3.4.3", "from": "espree@>=3.4.0 <4.0.0", "resolved": "https://registry.npmjs.org/espree/-/espree-3.4.3.tgz", "dev": true }, "esprima": { "version": "1.0.4", "from": "esprima@>=1.0.2 <1.1.0", "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.0.4.tgz", "dev": true }, "esquery": { "version": "1.0.0", "from": "esquery@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.0.tgz", "dev": true }, "esrecurse": { "version": "4.2.0", "from": "esrecurse@>=4.1.0 <5.0.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.0.tgz", "dev": true }, "estraverse": { "version": "4.2.0", "from": "estraverse@>=4.2.0 <5.0.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", "dev": true }, "esutils": { "version": "2.0.2", "from": "esutils@>=2.0.2 <3.0.0", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", "dev": true }, "event-emitter": { "version": "0.3.5", "from": "event-emitter@>=0.3.5 <0.4.0", "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", "dev": true }, "eventemitter2": { "version": "0.4.14", "from": "eventemitter2@>=0.4.13 <0.5.0", "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz", "dev": true }, "events": { "version": "1.1.1", "from": "events@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", "dev": true }, "evp_bytestokey": { "version": "1.0.0", "from": "evp_bytestokey@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.0.tgz", "dev": true }, "exit": { "version": "0.1.2", "from": "exit@>=0.1.1 <0.2.0", "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", "dev": true }, "exit-hook": { "version": "1.1.1", "from": "exit-hook@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", "dev": true }, "expand-brackets": { "version": "0.1.5", "from": "expand-brackets@>=0.1.4 <0.2.0", "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz" }, "expand-range": { "version": "1.8.2", "from": "expand-range@>=1.8.1 <2.0.0", "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz" }, "extend": { "version": "3.0.1", "from": "extend@>=3.0.0 <3.1.0", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz" }, "extglob": { "version": "0.3.2", "from": "extglob@>=0.3.1 <0.4.0", "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz" }, "extsprintf": { "version": "1.0.2", "from": "extsprintf@1.0.2", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz" }, "fast-levenshtein": { "version": "2.0.6", "from": "fast-levenshtein@>=2.0.4 <2.1.0", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "dev": true }, "faye-websocket": { "version": "0.10.0", "from": "faye-websocket@>=0.10.0 <0.11.0", "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", "dev": true }, "figures": { "version": "1.7.0", "from": "figures@>=1.3.5 <2.0.0", "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", "dev": true }, "file-entry-cache": { "version": "2.0.0", "from": "file-entry-cache@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", "dev": true }, "filename-regex": { "version": "2.0.1", "from": "filename-regex@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz" }, "fileset": { "version": "0.1.8", "from": "fileset@>=0.1.5 <0.2.0", "resolved": "https://registry.npmjs.org/fileset/-/fileset-0.1.8.tgz", "dev": true, "dependencies": { "glob": { "version": "3.2.11", "from": "glob@>=3.0.0 <4.0.0", "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz", "dev": true, "dependencies": { "minimatch": { "version": "0.3.0", "from": "minimatch@>=0.3.0 <0.4.0", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz", "dev": true } } }, "minimatch": { "version": "0.4.0", "from": "minimatch@>=0.0.0 <1.0.0", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.4.0.tgz", "dev": true } } }, "fill-range": { "version": "2.2.3", "from": "fill-range@>=2.1.0 <3.0.0", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz" }, "find-parent-dir": { "version": "0.3.0", "from": "find-parent-dir@>=0.3.0 <0.4.0", "resolved": "https://registry.npmjs.org/find-parent-dir/-/find-parent-dir-0.3.0.tgz", "dev": true }, "find-up": { "version": "1.1.2", "from": "find-up@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", "dev": true }, "findup-sync": { "version": "0.1.3", "from": "findup-sync@>=0.1.2 <0.2.0", "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.1.3.tgz", "dev": true, "dependencies": { "glob": { "version": "3.2.11", "from": "glob@>=3.2.9 <3.3.0", "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz", "dev": true }, "lodash": { "version": "2.4.2", "from": "lodash@>=2.4.1 <2.5.0", "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", "dev": true }, "minimatch": { "version": "0.3.0", "from": "minimatch@>=0.3.0 <0.4.0", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz", "dev": true } } }, "flat-cache": { "version": "1.2.2", "from": "flat-cache@>=1.2.1 <2.0.0", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.2.2.tgz", "dev": true }, "for-in": { "version": "1.0.2", "from": "for-in@>=1.0.1 <2.0.0", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz" }, "for-own": { "version": "0.1.5", "from": "for-own@>=0.1.4 <0.2.0", "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz" }, "forever-agent": { "version": "0.6.1", "from": "forever-agent@>=0.6.1 <0.7.0", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz" }, "form-data": { "version": "2.1.4", "from": "form-data@>=2.1.1 <2.2.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz" }, "fs-extra": { "version": "2.0.0", "from": "fs-extra@2.0.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-2.0.0.tgz" }, "fs.realpath": { "version": "1.0.0", "from": "fs.realpath@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "dev": true }, "fstream": { "version": "1.0.11", "from": "fstream@>=1.0.2 <2.0.0", "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", "dev": true }, "gaze": { "version": "1.1.2", "from": "gaze@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.2.tgz", "dev": true }, "generate-function": { "version": "2.0.0", "from": "generate-function@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz" }, "generate-object-property": { "version": "1.2.0", "from": "generate-object-property@>=1.1.0 <2.0.0", "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz" }, "get-caller-file": { "version": "1.0.2", "from": "get-caller-file@>=1.0.1 <2.0.0", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz", "dev": true }, "getobject": { "version": "0.1.0", "from": "getobject@>=0.1.0 <0.2.0", "resolved": "https://registry.npmjs.org/getobject/-/getobject-0.1.0.tgz", "dev": true }, "getpass": { "version": "0.1.7", "from": "getpass@>=0.1.1 <0.2.0", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "dependencies": { "assert-plus": { "version": "1.0.0", "from": "assert-plus@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" } } }, "glob": { "version": "7.1.1", "from": "glob@7.1.1", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz", "dev": true }, "glob-base": { "version": "0.3.0", "from": "glob-base@>=0.3.0 <0.4.0", "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz" }, "glob-parent": { "version": "2.0.0", "from": "glob-parent@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz" }, "global": { "version": "4.3.2", "from": "global@>=4.3.2 <5.0.0", "resolved": "https://registry.npmjs.org/global/-/global-4.3.2.tgz", "dev": true, "dependencies": { "process": { "version": "0.5.2", "from": "process@>=0.5.1 <0.6.0", "resolved": "https://registry.npmjs.org/process/-/process-0.5.2.tgz", "dev": true } } }, "globals": { "version": "9.18.0", "from": "globals@>=9.14.0 <10.0.0", "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", "dev": true }, "globby": { "version": "5.0.0", "from": "globby@>=5.0.0 <6.0.0", "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", "dev": true }, "globule": { "version": "1.2.0", "from": "globule@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/globule/-/globule-1.2.0.tgz", "dev": true }, "graceful-fs": { "version": "4.1.11", "from": "graceful-fs@>=4.1.2 <5.0.0", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz" }, "growl": { "version": "1.7.0", "from": "growl@>=1.7.0 <1.8.0", "resolved": "https://registry.npmjs.org/growl/-/growl-1.7.0.tgz", "dev": true }, "grunt": { "version": "0.4.5", "from": "grunt@0.4.5", "resolved": "https://registry.npmjs.org/grunt/-/grunt-0.4.5.tgz", "dev": true, "dependencies": { "async": { "version": "0.1.22", "from": "async@>=0.1.22 <0.2.0", "resolved": "https://registry.npmjs.org/async/-/async-0.1.22.tgz", "dev": true }, "glob": { "version": "3.1.21", "from": "glob@>=3.1.21 <3.2.0", "resolved": "https://registry.npmjs.org/glob/-/glob-3.1.21.tgz", "dev": true }, "graceful-fs": { "version": "1.2.3", "from": "graceful-fs@>=1.2.0 <1.3.0", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.2.3.tgz", "dev": true }, "inherits": { "version": "1.0.2", "from": "inherits@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/inherits/-/inherits-1.0.2.tgz", "dev": true }, "lodash": { "version": "0.9.2", "from": "lodash@>=0.9.2 <0.10.0", "resolved": "https://registry.npmjs.org/lodash/-/lodash-0.9.2.tgz", "dev": true }, "minimatch": { "version": "0.2.14", "from": "minimatch@>=0.2.12 <0.3.0", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", "dev": true }, "nopt": { "version": "1.0.10", "from": "nopt@>=1.0.10 <1.1.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", "dev": true } } }, "grunt-cleanempty": { "version": "1.0.3", "from": "grunt-cleanempty@1.0.3", "resolved": "https://registry.npmjs.org/grunt-cleanempty/-/grunt-cleanempty-1.0.3.tgz", "dev": true }, "grunt-cli": { "version": "0.1.9", "from": "grunt-cli@0.1.9", "resolved": "https://registry.npmjs.org/grunt-cli/-/grunt-cli-0.1.9.tgz", "dev": true, "dependencies": { "nopt": { "version": "1.0.10", "from": "nopt@~1.0.10", "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", "dev": true } } }, "grunt-contrib-clean": { "version": "0.4.1", "from": "grunt-contrib-clean@0.4.1", "resolved": "https://registry.npmjs.org/grunt-contrib-clean/-/grunt-contrib-clean-0.4.1.tgz", "dev": true }, "grunt-contrib-concat": { "version": "0.3.0", "from": "grunt-contrib-concat@0.3.0", "resolved": "https://registry.npmjs.org/grunt-contrib-concat/-/grunt-contrib-concat-0.3.0.tgz", "dev": true }, "grunt-contrib-copy": { "version": "0.4.1", "from": "grunt-contrib-copy@0.4.1", "resolved": "https://registry.npmjs.org/grunt-contrib-copy/-/grunt-contrib-copy-0.4.1.tgz", "dev": true }, "grunt-contrib-cssmin": { "version": "0.6.0", "from": "grunt-contrib-cssmin@0.6.0", "resolved": "https://registry.npmjs.org/grunt-contrib-cssmin/-/grunt-contrib-cssmin-0.6.0.tgz", "dev": true }, "grunt-contrib-htmlmin": { "version": "0.1.3", "from": "grunt-contrib-htmlmin@0.1.3", "resolved": "https://registry.npmjs.org/grunt-contrib-htmlmin/-/grunt-contrib-htmlmin-0.1.3.tgz", "dev": true }, "grunt-contrib-jasmine": { "version": "0.4.2", "from": "grunt-contrib-jasmine@0.4.2", "resolved": "https://registry.npmjs.org/grunt-contrib-jasmine/-/grunt-contrib-jasmine-0.4.2.tgz", "dev": true, "dependencies": { "graceful-fs": { "version": "1.1.14", "from": "graceful-fs@>=1.1.0 <1.2.0", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.1.14.tgz", "dev": true, "optional": true }, "rimraf": { "version": "2.0.3", "from": "rimraf@>=2.0.3 <2.1.0", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.0.3.tgz", "dev": true } } }, "grunt-contrib-less": { "version": "1.4.0", "from": "grunt-contrib-less@1.4.0", "resolved": "https://registry.npmjs.org/grunt-contrib-less/-/grunt-contrib-less-1.4.0.tgz", "dev": true }, "grunt-contrib-requirejs": { "version": "0.4.1", "from": "grunt-contrib-requirejs@0.4.1", "resolved": "https://registry.npmjs.org/grunt-contrib-requirejs/-/grunt-contrib-requirejs-0.4.1.tgz", "dev": true }, "grunt-contrib-uglify": { "version": "0.2.0", "from": "grunt-contrib-uglify@0.2.0", "resolved": "https://registry.npmjs.org/grunt-contrib-uglify/-/grunt-contrib-uglify-0.2.0.tgz", "dev": true }, "grunt-contrib-watch": { "version": "1.0.0", "from": "grunt-contrib-watch@1.0.0", "resolved": "https://registry.npmjs.org/grunt-contrib-watch/-/grunt-contrib-watch-1.0.0.tgz", "dev": true, "dependencies": { "async": { "version": "1.5.2", "from": "async@>=1.5.0 <2.0.0", "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", "dev": true }, "lodash": { "version": "3.10.1", "from": "lodash@>=3.10.1 <4.0.0", "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", "dev": true } } }, "grunt-eslint": { "version": "19.0.0", "from": "grunt-eslint@19.0.0", "resolved": "https://registry.npmjs.org/grunt-eslint/-/grunt-eslint-19.0.0.tgz", "dev": true }, "grunt-jasmine-node": { "version": "0.1.0", "from": "grunt-jasmine-node@0.1.0", "resolved": "https://registry.npmjs.org/grunt-jasmine-node/-/grunt-jasmine-node-0.1.0.tgz", "dev": true }, "grunt-legacy-log": { "version": "0.1.3", "from": "grunt-legacy-log@>=0.1.0 <0.2.0", "resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-0.1.3.tgz", "dev": true, "dependencies": { "lodash": { "version": "2.4.2", "from": "lodash@>=2.4.1 <2.5.0", "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", "dev": true }, "underscore.string": { "version": "2.3.3", "from": "underscore.string@>=2.3.3 <2.4.0", "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz", "dev": true } } }, "grunt-legacy-log-utils": { "version": "0.1.1", "from": "grunt-legacy-log-utils@>=0.1.1 <0.2.0", "resolved": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-0.1.1.tgz", "dev": true, "dependencies": { "lodash": { "version": "2.4.2", "from": "lodash@>=2.4.1 <2.5.0", "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", "dev": true }, "underscore.string": { "version": "2.3.3", "from": "underscore.string@>=2.3.3 <2.4.0", "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz", "dev": true } } }, "grunt-legacy-util": { "version": "0.2.0", "from": "grunt-legacy-util@>=0.2.0 <0.3.0", "resolved": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-0.2.0.tgz", "dev": true, "dependencies": { "async": { "version": "0.1.22", "from": "async@>=0.1.22 <0.2.0", "resolved": "https://registry.npmjs.org/async/-/async-0.1.22.tgz", "dev": true }, "lodash": { "version": "0.9.2", "from": "lodash@>=0.9.2 <0.10.0", "resolved": "https://registry.npmjs.org/lodash/-/lodash-0.9.2.tgz", "dev": true } } }, "grunt-lib-contrib": { "version": "0.6.1", "from": "grunt-lib-contrib@>=0.6.0 <0.7.0", "resolved": "https://registry.npmjs.org/grunt-lib-contrib/-/grunt-lib-contrib-0.6.1.tgz", "dev": true }, "grunt-lib-phantomjs": { "version": "0.3.0", "from": "grunt-lib-phantomjs@0.3.0", "resolved": "https://registry.npmjs.org/grunt-lib-phantomjs/-/grunt-lib-phantomjs-0.3.0.tgz", "dev": true, "dependencies": { "semver": { "version": "1.0.14", "from": "semver@>=1.0.14 <1.1.0", "resolved": "https://registry.npmjs.org/semver/-/semver-1.0.14.tgz", "dev": true } } }, "grunt-targethtml": { "version": "0.2.6", "from": "grunt-targethtml@0.2.6", "resolved": "https://registry.npmjs.org/grunt-targethtml/-/grunt-targethtml-0.2.6.tgz", "dev": true }, "grunt-template-jasmine-requirejs": { "version": "0.1.0", "from": "grunt-template-jasmine-requirejs@0.1.0", "resolved": "https://registry.npmjs.org/grunt-template-jasmine-requirejs/-/grunt-template-jasmine-requirejs-0.1.0.tgz", "dev": true }, "grunt-usemin": { "version": "0.1.11", "from": "grunt-usemin@0.1.11", "resolved": "https://registry.npmjs.org/grunt-usemin/-/grunt-usemin-0.1.11.tgz", "dev": true }, "har-validator": { "version": "2.0.6", "from": "har-validator@>=2.0.6 <2.1.0", "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz" }, "has-ansi": { "version": "2.0.0", "from": "has-ansi@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz" }, "has-flag": { "version": "1.0.0", "from": "has-flag@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", "dev": true }, "hash-base": { "version": "2.0.2", "from": "hash-base@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-2.0.2.tgz", "dev": true }, "hash.js": { "version": "1.1.3", "from": "hash.js@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.3.tgz", "dev": true }, "hawk": { "version": "3.1.3", "from": "hawk@>=3.1.3 <3.2.0", "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz" }, "hmac-drbg": { "version": "1.0.1", "from": "hmac-drbg@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", "dev": true }, "hoek": { "version": "2.16.3", "from": "hoek@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz" }, "hooker": { "version": "0.2.3", "from": "hooker@>=0.2.3 <0.3.0", "resolved": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz", "dev": true }, "hosted-git-info": { "version": "2.5.0", "from": "hosted-git-info@>=2.1.4 <3.0.0", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.5.0.tgz", "dev": true }, "html-minifier": { "version": "0.5.6", "from": "html-minifier@>=0.5.0 <0.6.0", "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-0.5.6.tgz", "dev": true }, "http-errors": { "version": "1.3.1", "from": "http-errors@>=1.3.1 <1.4.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.3.1.tgz", "dev": true }, "http-signature": { "version": "1.1.1", "from": "http-signature@>=1.1.0 <1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz" }, "https-browserify": { "version": "0.0.1", "from": "https-browserify@0.0.1", "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-0.0.1.tgz", "dev": true }, "husky": { "version": "0.13.2", "from": "husky@0.13.2", "resolved": "https://registry.npmjs.org/husky/-/husky-0.13.2.tgz", "dev": true, "dependencies": { "normalize-path": { "version": "1.0.0", "from": "normalize-path@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-1.0.0.tgz", "dev": true } } }, "iconv-lite": { "version": "0.2.11", "from": "iconv-lite@>=0.2.11 <0.3.0", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.2.11.tgz", "dev": true }, "ieee754": { "version": "1.1.8", "from": "ieee754@>=1.1.4 <2.0.0", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz", "dev": true }, "ignore": { "version": "3.3.3", "from": "ignore@>=3.2.0 <4.0.0", "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.3.tgz", "dev": true }, "image-size": { "version": "0.5.5", "from": "image-size@>=0.5.0 <0.6.0", "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", "dev": true, "optional": true }, "imurmurhash": { "version": "0.1.4", "from": "imurmurhash@>=0.1.4 <0.2.0", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "dev": true }, "indexof": { "version": "0.0.1", "from": "indexof@0.0.1", "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", "dev": true }, "inflight": { "version": "1.0.6", "from": "inflight@>=1.0.4 <2.0.0", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "dev": true }, "inherits": { "version": "2.0.3", "from": "inherits@>=2.0.1 <3.0.0", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz" }, "ini": { "version": "1.3.4", "from": "ini@>=1.2.0 <2.0.0", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz", "dev": true }, "inquirer": { "version": "0.12.0", "from": "inquirer@>=0.12.0 <0.13.0", "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.12.0.tgz", "dev": true }, "interpret": { "version": "1.0.3", "from": "interpret@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.0.3.tgz", "dev": true }, "invert-kv": { "version": "1.0.0", "from": "invert-kv@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", "dev": true }, "is-arrayish": { "version": "0.2.1", "from": "is-arrayish@>=0.2.1 <0.3.0", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "dev": true }, "is-binary-path": { "version": "1.0.1", "from": "is-binary-path@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz" }, "is-buffer": { "version": "1.1.5", "from": "is-buffer@>=1.1.5 <2.0.0", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.5.tgz" }, "is-builtin-module": { "version": "1.0.0", "from": "is-builtin-module@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", "dev": true }, "is-ci": { "version": "1.0.10", "from": "is-ci@>=1.0.9 <2.0.0", "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.0.10.tgz", "dev": true }, "is-dotfile": { "version": "1.0.3", "from": "is-dotfile@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz" }, "is-equal-shallow": { "version": "0.1.3", "from": "is-equal-shallow@>=0.1.3 <0.2.0", "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz" }, "is-extendable": { "version": "0.1.1", "from": "is-extendable@>=0.1.1 <0.2.0", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz" }, "is-extglob": { "version": "1.0.0", "from": "is-extglob@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz" }, "is-fullwidth-code-point": { "version": "1.0.0", "from": "is-fullwidth-code-point@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "dev": true }, "is-glob": { "version": "2.0.1", "from": "is-glob@>=2.0.1 <3.0.0", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz" }, "is-my-json-valid": { "version": "2.16.0", "from": "is-my-json-valid@>=2.12.4 <3.0.0", "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.16.0.tgz" }, "is-number": { "version": "2.1.0", "from": "is-number@>=2.1.0 <3.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz" }, "is-path-cwd": { "version": "1.0.0", "from": "is-path-cwd@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", "dev": true }, "is-path-in-cwd": { "version": "1.0.0", "from": "is-path-in-cwd@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz", "dev": true }, "is-path-inside": { "version": "1.0.0", "from": "is-path-inside@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.0.tgz", "dev": true }, "is-posix-bracket": { "version": "0.1.1", "from": "is-posix-bracket@>=0.1.0 <0.2.0", "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz" }, "is-primitive": { "version": "2.0.0", "from": "is-primitive@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz" }, "is-property": { "version": "1.0.2", "from": "is-property@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz" }, "is-resolvable": { "version": "1.0.0", "from": "is-resolvable@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.0.0.tgz", "dev": true }, "is-typedarray": { "version": "1.0.0", "from": "is-typedarray@>=1.0.0 <1.1.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz" }, "is-utf8": { "version": "0.2.1", "from": "is-utf8@>=0.2.0 <0.3.0", "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", "dev": true }, "isarray": { "version": "1.0.0", "from": "isarray@1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" }, "isobject": { "version": "2.1.0", "from": "isobject@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz" }, "isstream": { "version": "0.1.2", "from": "isstream@>=0.1.2 <0.2.0", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz" }, "jasmine-growl-reporter": { "version": "0.0.3", "from": "jasmine-growl-reporter@>=0.0.2 <0.1.0", "resolved": "https://registry.npmjs.org/jasmine-growl-reporter/-/jasmine-growl-reporter-0.0.3.tgz", "dev": true }, "jasmine-node": { "version": "1.11.0", "from": "jasmine-node@1.11.0", "resolved": "https://registry.npmjs.org/jasmine-node/-/jasmine-node-1.11.0.tgz", "dev": true, "dependencies": { "gaze": { "version": "0.3.4", "from": "gaze@>=0.3.2 <0.4.0", "resolved": "https://registry.npmjs.org/gaze/-/gaze-0.3.4.tgz", "dev": true }, "minimatch": { "version": "0.2.14", "from": "minimatch@>=0.2.9 <0.3.0", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", "dev": true }, "mkdirp": { "version": "0.3.5", "from": "mkdirp@>=0.3.5 <0.4.0", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", "dev": true } } }, "jasmine-reporters": { "version": "2.2.1", "from": "jasmine-reporters@>=0.2.0", "resolved": "https://registry.npmjs.org/jasmine-reporters/-/jasmine-reporters-2.2.1.tgz", "dev": true }, "js-tokens": { "version": "3.0.2", "from": "js-tokens@>=3.0.0 <4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", "dev": true }, "js-yaml": { "version": "2.0.5", "from": "js-yaml@>=2.0.5 <2.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-2.0.5.tgz", "dev": true }, "jsbn": { "version": "0.1.1", "from": "jsbn@>=0.1.0 <0.2.0", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "optional": true }, "json-loader": { "version": "0.5.7", "from": "json-loader@>=0.5.4 <0.6.0", "resolved": "https://registry.npmjs.org/json-loader/-/json-loader-0.5.7.tgz", "dev": true }, "json-schema": { "version": "0.2.3", "from": "json-schema@0.2.3", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz" }, "json-stable-stringify": { "version": "1.0.1", "from": "json-stable-stringify@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", "dev": true }, "json-stringify-safe": { "version": "5.0.1", "from": "json-stringify-safe@>=5.0.1 <5.1.0", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz" }, "json5": { "version": "0.5.1", "from": "json5@>=0.5.0 <0.6.0", "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", "dev": true }, "jsonfile": { "version": "2.4.0", "from": "jsonfile@>=2.1.0 <3.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz" }, "jsonify": { "version": "0.0.0", "from": "jsonify@>=0.0.0 <0.1.0", "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", "dev": true }, "jsonpointer": { "version": "4.0.1", "from": "jsonpointer@>=4.0.0 <5.0.0", "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz" }, "jsprim": { "version": "1.4.0", "from": "jsprim@>=1.2.2 <2.0.0", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.0.tgz", "dependencies": { "assert-plus": { "version": "1.0.0", "from": "assert-plus@1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" } } }, "junk": { "version": "1.0.3", "from": "junk@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/junk/-/junk-1.0.3.tgz", "dev": true }, "kew": { "version": "0.4.0", "from": "kew@0.4.0", "resolved": "https://registry.npmjs.org/kew/-/kew-0.4.0.tgz", "dev": true }, "keypress": { "version": "0.1.0", "from": "keypress@>=0.1.0 <0.2.0", "resolved": "https://registry.npmjs.org/keypress/-/keypress-0.1.0.tgz", "dev": true }, "kind-of": { "version": "3.2.2", "from": "kind-of@>=3.0.2 <4.0.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz" }, "lazy-cache": { "version": "1.0.4", "from": "lazy-cache@>=1.0.3 <2.0.0", "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", "dev": true }, "lcid": { "version": "1.0.0", "from": "lcid@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", "dev": true }, "less": { "version": "2.7.2", "from": "less@>=2.7.1 <2.8.0", "resolved": "https://registry.npmjs.org/less/-/less-2.7.2.tgz", "dev": true }, "levn": { "version": "0.3.0", "from": "levn@>=0.3.0 <0.4.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", "dev": true }, "livereload-js": { "version": "2.2.2", "from": "livereload-js@>=2.2.0 <3.0.0", "resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-2.2.2.tgz", "dev": true }, "load-grunt-tasks": { "version": "3.5.0", "from": "load-grunt-tasks@3.5.0", "resolved": "https://registry.npmjs.org/load-grunt-tasks/-/load-grunt-tasks-3.5.0.tgz", "dev": true }, "load-json-file": { "version": "1.1.0", "from": "load-json-file@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", "dev": true, "dependencies": { "strip-bom": { "version": "2.0.0", "from": "strip-bom@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", "dev": true } } }, "loader-runner": { "version": "2.3.0", "from": "loader-runner@>=2.3.0 <3.0.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.3.0.tgz", "dev": true }, "loader-utils": { "version": "0.2.17", "from": "loader-utils@>=0.2.16 <0.3.0", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz", "dev": true }, "lodash": { "version": "4.17.15", "from": "lodash@4.17.15", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz" }, "longest": { "version": "1.0.1", "from": "longest@>=1.0.1 <2.0.0", "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", "dev": true }, "lru-cache": { "version": "2.7.3", "from": "lru-cache@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", "dev": true }, "media-typer": { "version": "0.3.0", "from": "media-typer@0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "dev": true }, "memory-fs": { "version": "0.4.1", "from": "memory-fs@>=0.4.1 <0.5.0", "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", "dev": true }, "micromatch": { "version": "2.3.11", "from": "micromatch@>=2.1.5 <3.0.0", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz" }, "miller-rabin": { "version": "4.0.0", "from": "miller-rabin@>=4.0.0 <5.0.0", "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.0.tgz", "dev": true }, "mime": { "version": "1.2.11", "from": "mime@>=1.2.11 <1.3.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.2.11.tgz", "dev": true, "optional": true }, "mime-db": { "version": "1.29.0", "from": "mime-db@>=1.29.0 <1.30.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.29.0.tgz" }, "mime-types": { "version": "2.1.16", "from": "mime-types@>=2.1.7 <2.2.0", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.16.tgz" }, "min-document": { "version": "2.19.0", "from": "min-document@>=2.19.0 <3.0.0", "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", "dev": true }, "minimalistic-assert": { "version": "1.0.0", "from": "minimalistic-assert@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz", "dev": true }, "minimalistic-crypto-utils": { "version": "1.0.1", "from": "minimalistic-crypto-utils@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", "dev": true }, "minimatch": { "version": "3.0.4", "from": "minimatch@>=3.0.2 <4.0.0", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz" }, "minimist": { "version": "0.0.8", "from": "minimist@0.0.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "dev": true }, "mkdirp": { "version": "0.5.1", "from": "mkdirp@>=0.5.0 <0.6.0", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "dev": true }, "mkpath": { "version": "0.1.0", "from": "mkpath@>=0.1.0 <0.2.0", "resolved": "https://registry.npmjs.org/mkpath/-/mkpath-0.1.0.tgz" }, "ms": { "version": "0.7.1", "from": "ms@0.7.1", "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", "dev": true }, "multimatch": { "version": "2.1.0", "from": "multimatch@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-2.1.0.tgz", "dev": true }, "mute-stream": { "version": "0.0.5", "from": "mute-stream@0.0.5", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz", "dev": true }, "nan": { "version": "1.0.0", "from": "nan@>=1.0.0 <1.1.0", "resolved": "https://registry.npmjs.org/nan/-/nan-1.0.0.tgz" }, "natural-compare": { "version": "1.4.0", "from": "natural-compare@>=1.4.0 <2.0.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "dev": true }, "node-libs-browser": { "version": "2.0.0", "from": "node-libs-browser@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.0.0.tgz", "dev": true, "dependencies": { "string_decoder": { "version": "0.10.31", "from": "string_decoder@>=0.10.25 <0.11.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", "dev": true } } }, "nopt": { "version": "3.0.6", "from": "nopt@>=3.0.1 <4.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz" }, "normalize-package-data": { "version": "2.4.0", "from": "normalize-package-data@>=2.3.2 <3.0.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", "dev": true }, "normalize-path": { "version": "2.1.1", "from": "normalize-path@>=2.0.1 <3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz" }, "npm": { "version": "3.10.10", "from": "npm@3.10.10", "resolved": "https://registry.npmjs.org/npm/-/npm-3.10.10.tgz", "dependencies": { "abbrev": { "version": "1.0.9", "from": "abbrev@1.0.9", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz" }, "ansi-regex": { "version": "2.0.0", "from": "ansi-regex@2.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.0.0.tgz" }, "ansicolors": { "version": "0.3.2", "from": "ansicolors@>=0.3.2 <0.4.0", "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz" }, "ansistyles": { "version": "0.1.3", "from": "ansistyles@>=0.1.3 <0.2.0", "resolved": "https://registry.npmjs.org/ansistyles/-/ansistyles-0.1.3.tgz" }, "aproba": { "version": "1.0.4", "from": "aproba@>=1.0.3 <1.1.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.0.4.tgz" }, "archy": { "version": "1.0.0", "from": "archy@>=1.0.0 <1.1.0", "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz" }, "asap": { "version": "2.0.5", "from": "asap@2.0.5", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.5.tgz" }, "chownr": { "version": "1.0.1", "from": "chownr@>=1.0.1 <1.1.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.0.1.tgz" }, "cmd-shim": { "version": "2.0.2", "from": "cmd-shim@2.0.2", "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-2.0.2.tgz" }, "columnify": { "version": "1.5.4", "from": "columnify@1.5.4", "resolved": "https://registry.npmjs.org/columnify/-/columnify-1.5.4.tgz", "dependencies": { "wcwidth": { "version": "1.0.0", "from": "wcwidth@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.0.tgz", "dependencies": { "defaults": { "version": "1.0.3", "from": "defaults@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", "dependencies": { "clone": { "version": "1.0.2", "from": "clone@>=1.0.2 <2.0.0", "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.2.tgz" } } } } } } }, "config-chain": { "version": "1.1.11", "from": "config-chain@1.1.11", "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.11.tgz", "dependencies": { "proto-list": { "version": "1.2.4", "from": "proto-list@>=1.2.1 <1.3.0", "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz" } } }, "debuglog": { "version": "1.0.1", "from": "debuglog@1.0.1", "resolved": "https://registry.npmjs.org/debuglog/-/debuglog-1.0.1.tgz" }, "dezalgo": { "version": "1.0.3", "from": "dezalgo@>=1.0.3 <1.1.0" }, "editor": { "version": "1.0.0", "from": "editor@>=1.0.0 <1.1.0", "resolved": "https://registry.npmjs.org/editor/-/editor-1.0.0.tgz" }, "fs-vacuum": { "version": "1.2.9", "from": "fs-vacuum@latest", "resolved": "https://registry.npmjs.org/fs-vacuum/-/fs-vacuum-1.2.9.tgz" }, "fs-write-stream-atomic": { "version": "1.0.8", "from": "fs-write-stream-atomic@1.0.8" }, "fstream": { "version": "1.0.10", "from": "fstream@>=1.0.10 <1.1.0", "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.10.tgz" }, "fstream-npm": { "version": "1.2.0", "from": "fstream-npm@latest", "resolved": "https://registry.npmjs.org/fstream-npm/-/fstream-npm-1.2.0.tgz", "dependencies": { "fstream-ignore": { "version": "1.0.5", "from": "fstream-ignore@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/fstream-ignore/-/fstream-ignore-1.0.5.tgz", "dependencies": { "minimatch": { "version": "3.0.3", "from": "minimatch@>=3.0.0 <4.0.0", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.3.tgz", "dependencies": { "brace-expansion": { "version": "1.1.6", "from": "brace-expansion@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.6.tgz", "dependencies": { "balanced-match": { "version": "0.4.2", "from": "balanced-match@>=0.4.1 <0.5.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz" }, "concat-map": { "version": "0.0.1", "from": "concat-map@0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" } } } } } } } } }, "glob": { "version": "7.1.0", "from": "glob@7.1.0", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.0.tgz", "dependencies": { "fs.realpath": { "version": "1.0.0", "from": "fs.realpath@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" }, "minimatch": { "version": "3.0.3", "from": "minimatch@>=3.0.2 <4.0.0", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.3.tgz", "dependencies": { "brace-expansion": { "version": "1.1.6", "from": "brace-expansion@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.6.tgz", "dependencies": { "balanced-match": { "version": "0.4.2", "from": "balanced-match@>=0.4.1 <0.5.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz" }, "concat-map": { "version": "0.0.1", "from": "concat-map@0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" } } } } }, "path-is-absolute": { "version": "1.0.1", "from": "path-is-absolute@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" } } }, "graceful-fs": { "version": "4.1.9", "from": "graceful-fs@4.1.9", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.9.tgz" }, "has-unicode": { "version": "2.0.1", "from": "has-unicode@>=2.0.0 <2.1.0", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz" }, "hosted-git-info": { "version": "2.1.5", "from": "hosted-git-info@latest", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.1.5.tgz" }, "iferr": { "version": "0.1.5", "from": "iferr@>=0.1.5 <0.2.0", "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz" }, "imurmurhash": { "version": "0.1.4", "from": "imurmurhash@>=0.1.4 <0.2.0", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz" }, "inflight": { "version": "1.0.5", "from": "inflight@latest", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.5.tgz" }, "inherits": { "version": "2.0.3", "from": "inherits@2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz" }, "ini": { "version": "1.3.4", "from": "ini@>=1.3.4 <1.4.0", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz" }, "init-package-json": { "version": "1.9.4", "from": "init-package-json@latest", "resolved": "https://registry.npmjs.org/init-package-json/-/init-package-json-1.9.4.tgz", "dependencies": { "glob": { "version": "6.0.4", "from": "glob@>=6.0.0 <7.0.0", "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", "dependencies": { "minimatch": { "version": "3.0.3", "from": "minimatch@>=2.0.0 <3.0.0||>=3.0.0 <4.0.0", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.3.tgz", "dependencies": { "brace-expansion": { "version": "1.1.6", "from": "brace-expansion@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.6.tgz", "dependencies": { "balanced-match": { "version": "0.4.2", "from": "balanced-match@>=0.4.1 <0.5.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz" }, "concat-map": { "version": "0.0.1", "from": "concat-map@0.0.1" } } } } }, "path-is-absolute": { "version": "1.0.0", "from": "path-is-absolute@>=1.0.0 <2.0.0" } } }, "promzard": { "version": "0.3.0", "from": "promzard@>=0.3.0 <0.4.0", "resolved": "https://registry.npmjs.org/promzard/-/promzard-0.3.0.tgz" } } }, "lockfile": { "version": "1.0.2", "from": "lockfile@1.0.2", "resolved": "https://registry.npmjs.org/lockfile/-/lockfile-1.0.2.tgz" }, "lodash._baseindexof": { "version": "3.1.0", "from": "lodash._baseindexof@3.1.0", "resolved": "https://registry.npmjs.org/lodash._baseindexof/-/lodash._baseindexof-3.1.0.tgz" }, "lodash._baseuniq": { "version": "4.6.0", "from": "lodash._baseuniq@latest", "resolved": "https://registry.npmjs.org/lodash._baseuniq/-/lodash._baseuniq-4.6.0.tgz", "dependencies": { "lodash._createset": { "version": "4.0.3", "from": "lodash._createset@>=4.0.0 <4.1.0", "resolved": "https://registry.npmjs.org/lodash._createset/-/lodash._createset-4.0.3.tgz" }, "lodash._root": { "version": "3.0.1", "from": "lodash._root@>=3.0.0 <3.1.0", "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz" } } }, "lodash._bindcallback": { "version": "3.0.1", "from": "lodash._bindcallback@3.0.1", "resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz" }, "lodash._cacheindexof": { "version": "3.0.2", "from": "lodash._cacheindexof@3.0.2", "resolved": "https://registry.npmjs.org/lodash._cacheindexof/-/lodash._cacheindexof-3.0.2.tgz" }, "lodash._createcache": { "version": "3.1.2", "from": "lodash._createcache@3.1.2", "resolved": "https://registry.npmjs.org/lodash._createcache/-/lodash._createcache-3.1.2.tgz" }, "lodash._getnative": { "version": "3.9.1", "from": "lodash._getnative@3.9.1", "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz" }, "lodash.clonedeep": { "version": "4.5.0", "from": "lodash.clonedeep@4.5.0", "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz" }, "lodash.restparam": { "version": "3.6.1", "from": "lodash.restparam@3.6.1", "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz" }, "lodash.union": { "version": "4.6.0", "from": "lodash.union@4.6.0", "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz" }, "lodash.uniq": { "version": "4.5.0", "from": "lodash.uniq@4.5.0", "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz" }, "lodash.without": { "version": "4.4.0", "from": "lodash.without@4.4.0", "resolved": "https://registry.npmjs.org/lodash.without/-/lodash.without-4.4.0.tgz" }, "mkdirp": { "version": "0.5.1", "from": "mkdirp@>=0.5.1 <0.6.0", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "dependencies": { "minimist": { "version": "0.0.8", "from": "minimist@0.0.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz" } } }, "node-gyp": { "version": "3.4.0", "from": "node-gyp@latest", "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.4.0.tgz", "dependencies": { "minimatch": { "version": "3.0.3", "from": "minimatch@>=3.0.2 <4.0.0", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.3.tgz", "dependencies": { "brace-expansion": { "version": "1.1.6", "from": "brace-expansion@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.6.tgz", "dependencies": { "balanced-match": { "version": "0.4.2", "from": "balanced-match@>=0.4.1 <0.5.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz" }, "concat-map": { "version": "0.0.1", "from": "concat-map@0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" } } } } }, "npmlog": { "version": "3.1.2", "from": "npmlog@>=0.0.0 <1.0.0||>=1.0.0 <2.0.0||>=2.0.0 <3.0.0||>=3.0.0 <4.0.0", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-3.1.2.tgz", "dependencies": { "are-we-there-yet": { "version": "1.1.2", "from": "are-we-there-yet@~1.1.2", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.2.tgz", "dependencies": { "delegates": { "version": "1.0.0", "from": "delegates@^1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz" } } }, "console-control-strings": { "version": "1.1.0", "from": "console-control-strings@~1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz" }, "gauge": { "version": "2.6.0", "from": "gauge@~2.6.0", "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.6.0.tgz", "dependencies": { "has-color": { "version": "0.1.7", "from": "has-color@^0.1.7", "resolved": "https://registry.npmjs.org/has-color/-/has-color-0.1.7.tgz" }, "object-assign": { "version": "4.1.0", "from": "object-assign@^4.1.0", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.0.tgz" }, "signal-exit": { "version": "3.0.0", "from": "signal-exit@^3.0.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.0.tgz" }, "string-width": { "version": "1.0.2", "from": "string-width@^1.0.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "dependencies": { "code-point-at": { "version": "1.0.0", "from": "code-point-at@^1.0.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.0.0.tgz", "dependencies": { "number-is-nan": { "version": "1.0.0", "from": "number-is-nan@^1.0.0", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.0.tgz" } } }, "is-fullwidth-code-point": { "version": "1.0.0", "from": "is-fullwidth-code-point@^1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "dependencies": { "number-is-nan": { "version": "1.0.0", "from": "number-is-nan@^1.0.0", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.0.tgz" } } } } }, "wide-align": { "version": "1.1.0", "from": "wide-align@^1.1.0", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.0.tgz" } } }, "set-blocking": { "version": "2.0.0", "from": "set-blocking@~2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz" } } }, "path-array": { "version": "1.0.1", "from": "path-array@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/path-array/-/path-array-1.0.1.tgz", "dependencies": { "array-index": { "version": "1.0.0", "from": "array-index@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/array-index/-/array-index-1.0.0.tgz", "dependencies": { "debug": { "version": "2.2.0", "from": "debug@>=2.2.0 <3.0.0", "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", "dependencies": { "ms": { "version": "0.7.1", "from": "ms@0.7.1", "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz" } } }, "es6-symbol": { "version": "3.1.0", "from": "es6-symbol@>=3.0.2 <4.0.0", "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.0.tgz", "dependencies": { "d": { "version": "0.1.1", "from": "d@>=0.1.1 <0.2.0", "resolved": "https://registry.npmjs.org/d/-/d-0.1.1.tgz" }, "es5-ext": { "version": "0.10.12", "from": "es5-ext@>=0.10.11 <0.11.0", "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.12.tgz", "dependencies": { "es6-iterator": { "version": "2.0.0", "from": "es6-iterator@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.0.tgz" } } } } } } } } } } }, "nopt": { "version": "3.0.6", "from": "nopt@>=3.0.6 <3.1.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz" }, "normalize-git-url": { "version": "3.0.2", "from": "normalize-git-url@>=3.0.2 <3.1.0" }, "normalize-package-data": { "version": "2.3.5", "from": "normalize-package-data@>=2.3.5 <2.4.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.3.5.tgz", "dependencies": { "is-builtin-module": { "version": "1.0.0", "from": "is-builtin-module@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", "dependencies": { "builtin-modules": { "version": "1.1.1", "from": "builtin-modules@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz" } } } } }, "npm-cache-filename": { "version": "1.0.2", "from": "npm-cache-filename@>=1.0.2 <1.1.0", "resolved": "https://registry.npmjs.org/npm-cache-filename/-/npm-cache-filename-1.0.2.tgz" }, "npm-install-checks": { "version": "3.0.0", "from": "npm-install-checks@3.0.0" }, "npm-package-arg": { "version": "4.2.0", "from": "npm-package-arg@4.2.0", "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-4.2.0.tgz" }, "npm-registry-client": { "version": "7.2.1", "from": "npm-registry-client@7.2.1", "resolved": "https://registry.npmjs.org/npm-registry-client/-/npm-registry-client-7.2.1.tgz", "dependencies": { "concat-stream": { "version": "1.5.2", "from": "concat-stream@>=1.5.2 <2.0.0", "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.2.tgz", "dependencies": { "readable-stream": { "version": "2.0.6", "from": "readable-stream@>=2.0.0 <2.1.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", "dependencies": { "core-util-is": { "version": "1.0.2", "from": "core-util-is@>=1.0.0 <1.1.0" }, "isarray": { "version": "1.0.0", "from": "isarray@>=1.0.0 <1.1.0" }, "process-nextick-args": { "version": "1.0.7", "from": "process-nextick-args@>=1.0.6 <1.1.0", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz" }, "string_decoder": { "version": "0.10.31", "from": "string_decoder@>=0.10.0 <0.11.0" }, "util-deprecate": { "version": "1.0.2", "from": "util-deprecate@>=1.0.1 <1.1.0" } } }, "typedarray": { "version": "0.0.6", "from": "typedarray@>=0.0.5 <0.1.0", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz" } } }, "npmlog": { "version": "3.1.2", "from": "npmlog@>=2.0.0 <2.1.0||>=3.1.0 <3.2.0", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-3.1.2.tgz", "optional": true, "dependencies": { "are-we-there-yet": { "version": "1.1.2", "from": "are-we-there-yet@~1.1.2", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.2.tgz", "optional": true, "dependencies": { "delegates": { "version": "1.0.0", "from": "delegates@^1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", "optional": true } } }, "console-control-strings": { "version": "1.1.0", "from": "console-control-strings@~1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz" }, "gauge": { "version": "2.6.0", "from": "gauge@~2.6.0", "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.6.0.tgz", "optional": true, "dependencies": { "has-color": { "version": "0.1.7", "from": "has-color@^0.1.7", "resolved": "https://registry.npmjs.org/has-color/-/has-color-0.1.7.tgz", "optional": true }, "object-assign": { "version": "4.1.0", "from": "object-assign@^4.1.0", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.0.tgz", "optional": true }, "signal-exit": { "version": "3.0.0", "from": "signal-exit@^3.0.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.0.tgz", "optional": true }, "string-width": { "version": "1.0.2", "from": "string-width@^1.0.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "dependencies": { "code-point-at": { "version": "1.0.0", "from": "code-point-at@^1.0.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.0.0.tgz", "dependencies": { "number-is-nan": { "version": "1.0.0", "from": "number-is-nan@^1.0.0", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.0.tgz" } } }, "is-fullwidth-code-point": { "version": "1.0.0", "from": "is-fullwidth-code-point@^1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "dependencies": { "number-is-nan": { "version": "1.0.0", "from": "number-is-nan@^1.0.0", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.0.tgz" } } } } }, "wide-align": { "version": "1.1.0", "from": "wide-align@^1.1.0", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.0.tgz", "optional": true } } }, "set-blocking": { "version": "2.0.0", "from": "set-blocking@~2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "optional": true } } }, "retry": { "version": "0.10.0", "from": "retry@>=0.10.0 <0.11.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.10.0.tgz" } } }, "npm-user-validate": { "version": "0.1.5", "from": "npm-user-validate@0.1.5", "resolved": "https://registry.npmjs.org/npm-user-validate/-/npm-user-validate-0.1.5.tgz" }, "npmlog": { "version": "4.0.0", "from": "npmlog@4.0.0", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.0.0.tgz", "dependencies": { "are-we-there-yet": { "version": "1.1.2", "from": "are-we-there-yet@>=1.1.2 <1.2.0", "resolved": "http://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.2.tgz", "dependencies": { "delegates": { "version": "1.0.0", "from": "delegates@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz" } } }, "console-control-strings": { "version": "1.1.0", "from": "console-control-strings@>=1.1.0 <1.2.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz" }, "gauge": { "version": "2.6.0", "from": "gauge@>=2.6.0 <2.7.0", "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.6.0.tgz", "dependencies": { "has-color": { "version": "0.1.7", "from": "has-color@>=0.1.7 <0.2.0", "resolved": "https://registry.npmjs.org/has-color/-/has-color-0.1.7.tgz" }, "object-assign": { "version": "4.1.0", "from": "object-assign@>=4.0.1 <5.0.0", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.0.tgz" }, "signal-exit": { "version": "3.0.0", "from": "signal-exit@>=3.0.0 <4.0.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.0.tgz" }, "string-width": { "version": "1.0.2", "from": "string-width@>=1.0.1 <2.0.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "dependencies": { "code-point-at": { "version": "1.0.0", "from": "code-point-at@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.0.0.tgz", "dependencies": { "number-is-nan": { "version": "1.0.0", "from": "number-is-nan@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.0.tgz" } } }, "is-fullwidth-code-point": { "version": "1.0.0", "from": "is-fullwidth-code-point@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "dependencies": { "number-is-nan": { "version": "1.0.0", "from": "number-is-nan@^1.0.0", "resolved": "http://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.0.tgz" } } } } }, "wide-align": { "version": "1.1.0", "from": "wide-align@>=1.1.0 <2.0.0", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.0.tgz" } } }, "set-blocking": { "version": "2.0.0", "from": "set-blocking@>=2.0.0 <2.1.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz" } } }, "once": { "version": "1.4.0", "from": "once@1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz" }, "opener": { "version": "1.4.2", "from": "opener@1.4.2", "resolved": "https://registry.npmjs.org/opener/-/opener-1.4.2.tgz" }, "osenv": { "version": "0.1.3", "from": "osenv@>=0.1.3 <0.2.0", "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.3.tgz", "dependencies": { "os-homedir": { "version": "1.0.1", "from": "os-homedir@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.1.tgz" }, "os-tmpdir": { "version": "1.0.1", "from": "os-tmpdir@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.1.tgz" } } }, "path-is-inside": { "version": "1.0.2", "from": "path-is-inside@1.0.2", "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz" }, "read": { "version": "1.0.7", "from": "read@>=1.0.7 <1.1.0", "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", "dependencies": { "mute-stream": { "version": "0.0.5", "from": "mute-stream@>=0.0.4 <0.1.0", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz" } } }, "read-cmd-shim": { "version": "1.0.1", "from": "read-cmd-shim@>=1.0.1 <1.1.0", "resolved": "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-1.0.1.tgz" }, "read-installed": { "version": "4.0.3", "from": "read-installed@>=4.0.3 <4.1.0", "resolved": "https://registry.npmjs.org/read-installed/-/read-installed-4.0.3.tgz", "dependencies": { "util-extend": { "version": "1.0.3", "from": "util-extend@>=1.0.1 <2.0.0", "resolved": "https://registry.npmjs.org/util-extend/-/util-extend-1.0.3.tgz" } } }, "read-package-json": { "version": "2.0.4", "from": "read-package-json@>=2.0.3 <2.1.0", "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-2.0.4.tgz", "dependencies": { "glob": { "version": "6.0.4", "from": "glob@>=6.0.0 <7.0.0", "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", "dependencies": { "minimatch": { "version": "3.0.3", "from": "minimatch@>=2.0.0 <3.0.0||>=3.0.0 <4.0.0", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.3.tgz", "dependencies": { "brace-expansion": { "version": "1.1.6", "from": "brace-expansion@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.6.tgz", "dependencies": { "balanced-match": { "version": "0.4.2", "from": "balanced-match@>=0.4.1 <0.5.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz" }, "concat-map": { "version": "0.0.1", "from": "concat-map@0.0.1" } } } } }, "path-is-absolute": { "version": "1.0.0", "from": "path-is-absolute@>=1.0.0 <2.0.0" } } }, "json-parse-helpfulerror": { "version": "1.0.3", "from": "json-parse-helpfulerror@>=1.0.2 <2.0.0", "resolved": "https://registry.npmjs.org/json-parse-helpfulerror/-/json-parse-helpfulerror-1.0.3.tgz", "dependencies": { "jju": { "version": "1.3.0", "from": "jju@>=1.1.0 <2.0.0", "resolved": "https://registry.npmjs.org/jju/-/jju-1.3.0.tgz" } } } } }, "read-package-tree": { "version": "5.1.5", "from": "read-package-tree@>=5.1.4 <5.2.0", "resolved": "https://registry.npmjs.org/read-package-tree/-/read-package-tree-5.1.5.tgz" }, "readable-stream": { "version": "2.1.5", "from": "readable-stream@2.1.5", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.1.5.tgz", "dependencies": { "buffer-shims": { "version": "1.0.0", "from": "buffer-shims@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz" }, "core-util-is": { "version": "1.0.2", "from": "core-util-is@>=1.0.0 <1.1.0" }, "isarray": { "version": "1.0.0", "from": "isarray@>=1.0.0 <1.1.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" }, "process-nextick-args": { "version": "1.0.7", "from": "process-nextick-args@>=1.0.6 <1.1.0", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz" }, "string_decoder": { "version": "0.10.31", "from": "string_decoder@>=0.10.0 <0.11.0" }, "util-deprecate": { "version": "1.0.2", "from": "util-deprecate@>=1.0.1 <1.1.0", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" } } }, "readdir-scoped-modules": { "version": "1.0.2", "from": "readdir-scoped-modules@1.0.2", "resolved": "https://registry.npmjs.org/readdir-scoped-modules/-/readdir-scoped-modules-1.0.2.tgz" }, "realize-package-specifier": { "version": "3.0.3", "from": "realize-package-specifier@>=3.0.2 <3.1.0" }, "request": { "version": "2.75.0", "from": "request@2.75.0", "resolved": "https://registry.npmjs.org/request/-/request-2.75.0.tgz", "dependencies": { "aws-sign2": { "version": "0.6.0", "from": "aws-sign2@>=0.6.0 <0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz" }, "aws4": { "version": "1.4.1", "from": "aws4@>=1.2.1 <2.0.0", "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.4.1.tgz" }, "bl": { "version": "1.1.2", "from": "bl@>=1.1.2 <1.2.0", "resolved": "https://registry.npmjs.org/bl/-/bl-1.1.2.tgz", "dependencies": { "readable-stream": { "version": "2.0.6", "from": "readable-stream@>=2.0.5 <2.1.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", "dependencies": { "core-util-is": { "version": "1.0.2", "from": "core-util-is@>=1.0.0 <1.1.0", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz" }, "isarray": { "version": "1.0.0", "from": "isarray@>=1.0.0 <1.1.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" }, "process-nextick-args": { "version": "1.0.7", "from": "process-nextick-args@>=1.0.6 <1.1.0", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz" }, "string_decoder": { "version": "0.10.31", "from": "string_decoder@>=0.10.0 <0.11.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz" }, "util-deprecate": { "version": "1.0.2", "from": "util-deprecate@>=1.0.1 <1.1.0", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" } } } } }, "caseless": { "version": "0.11.0", "from": "caseless@>=0.11.0 <0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz" }, "combined-stream": { "version": "1.0.5", "from": "combined-stream@>=1.0.5 <1.1.0", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", "dependencies": { "delayed-stream": { "version": "1.0.0", "from": "delayed-stream@>=1.0.0 <1.1.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" } } }, "extend": { "version": "3.0.0", "from": "extend@>=3.0.0 <3.1.0", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.0.tgz" }, "forever-agent": { "version": "0.6.1", "from": "forever-agent@>=0.6.1 <0.7.0", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz" }, "form-data": { "version": "2.0.0", "from": "form-data@>=2.0.0 <2.1.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.0.0.tgz", "dependencies": { "asynckit": { "version": "0.4.0", "from": "asynckit@>=0.4.0 <0.5.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" } } }, "har-validator": { "version": "2.0.6", "from": "har-validator@>=2.0.6 <2.1.0", "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz", "dependencies": { "chalk": { "version": "1.1.3", "from": "chalk@>=1.1.1 <2.0.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "dependencies": { "ansi-styles": { "version": "2.2.1", "from": "ansi-styles@>=2.2.1 <3.0.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz" }, "escape-string-regexp": { "version": "1.0.5", "from": "escape-string-regexp@>=1.0.2 <2.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" }, "has-ansi": { "version": "2.0.0", "from": "has-ansi@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz" }, "supports-color": { "version": "2.0.0", "from": "supports-color@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz" } } }, "commander": { "version": "2.9.0", "from": "commander@>=2.9.0 <3.0.0", "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", "dependencies": { "graceful-readlink": { "version": "1.0.1", "from": "graceful-readlink@>=1.0.0", "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz" } } }, "is-my-json-valid": { "version": "2.15.0", "from": "is-my-json-valid@>=2.12.4 <3.0.0", "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.15.0.tgz", "dependencies": { "generate-function": { "version": "2.0.0", "from": "generate-function@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz" }, "generate-object-property": { "version": "1.2.0", "from": "generate-object-property@>=1.1.0 <2.0.0", "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", "dependencies": { "is-property": { "version": "1.0.2", "from": "is-property@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz" } } }, "jsonpointer": { "version": "4.0.0", "from": "jsonpointer@>=4.0.0 <5.0.0", "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.0.tgz" }, "xtend": { "version": "4.0.1", "from": "xtend@>=4.0.0 <5.0.0", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz" } } }, "pinkie-promise": { "version": "2.0.1", "from": "pinkie-promise@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", "dependencies": { "pinkie": { "version": "2.0.4", "from": "pinkie@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz" } } } } }, "hawk": { "version": "3.1.3", "from": "hawk@>=3.1.3 <3.2.0", "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", "dependencies": { "boom": { "version": "2.10.1", "from": "boom@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz" }, "cryptiles": { "version": "2.0.5", "from": "cryptiles@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz" }, "hoek": { "version": "2.16.3", "from": "hoek@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz" }, "sntp": { "version": "1.0.9", "from": "sntp@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz" } } }, "http-signature": { "version": "1.1.1", "from": "http-signature@>=1.1.0 <1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", "dependencies": { "assert-plus": { "version": "0.2.0", "from": "assert-plus@>=0.2.0 <0.3.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz" }, "jsprim": { "version": "1.3.1", "from": "jsprim@>=1.2.2 <2.0.0", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.3.1.tgz", "dependencies": { "extsprintf": { "version": "1.0.2", "from": "extsprintf@1.0.2", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz" }, "json-schema": { "version": "0.2.3", "from": "json-schema@0.2.3", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz" }, "verror": { "version": "1.3.6", "from": "verror@1.3.6", "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz" } } }, "sshpk": { "version": "1.10.1", "from": "sshpk@>=1.7.0 <2.0.0", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.10.1.tgz", "dependencies": { "asn1": { "version": "0.2.3", "from": "asn1@>=0.2.3 <0.3.0", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz" }, "assert-plus": { "version": "1.0.0", "from": "assert-plus@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" }, "bcrypt-pbkdf": { "version": "1.0.0", "from": "bcrypt-pbkdf@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.0.tgz", "optional": true }, "dashdash": { "version": "1.14.0", "from": "dashdash@>=1.12.0 <2.0.0", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.0.tgz" }, "ecc-jsbn": { "version": "0.1.1", "from": "ecc-jsbn@>=0.1.1 <0.2.0", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", "optional": true }, "getpass": { "version": "0.1.6", "from": "getpass@>=0.1.1 <0.2.0", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.6.tgz" }, "jodid25519": { "version": "1.0.2", "from": "jodid25519@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/jodid25519/-/jodid25519-1.0.2.tgz", "optional": true }, "jsbn": { "version": "0.1.0", "from": "jsbn@>=0.1.0 <0.2.0", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.0.tgz", "optional": true }, "tweetnacl": { "version": "0.14.3", "from": "tweetnacl@>=0.14.0 <0.15.0", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.3.tgz", "optional": true } } } } }, "is-typedarray": { "version": "1.0.0", "from": "is-typedarray@>=1.0.0 <1.1.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz" }, "isstream": { "version": "0.1.2", "from": "isstream@>=0.1.2 <0.2.0", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz" }, "json-stringify-safe": { "version": "5.0.1", "from": "json-stringify-safe@>=5.0.1 <5.1.0", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz" }, "mime-types": { "version": "2.1.12", "from": "mime-types@>=2.1.7 <2.2.0", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.12.tgz", "dependencies": { "mime-db": { "version": "1.24.0", "from": "mime-db@>=1.24.0 <1.25.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.24.0.tgz" } } }, "node-uuid": { "version": "1.4.7", "from": "node-uuid@>=1.4.7 <1.5.0", "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.7.tgz" }, "oauth-sign": { "version": "0.8.2", "from": "oauth-sign@>=0.8.1 <0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz" }, "qs": { "version": "6.2.1", "from": "qs@>=6.2.0 <6.3.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.2.1.tgz" }, "stringstream": { "version": "0.0.5", "from": "stringstream@>=0.0.4 <0.1.0", "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz" }, "tough-cookie": { "version": "2.3.1", "from": "tough-cookie@>=2.3.0 <2.4.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.1.tgz" }, "tunnel-agent": { "version": "0.4.3", "from": "tunnel-agent@>=0.4.1 <0.5.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz" } } }, "retry": { "version": "0.10.0", "from": "retry@0.10.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.10.0.tgz" }, "rimraf": { "version": "2.5.4", "from": "rimraf@2.5.4", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.4.tgz" }, "semver": { "version": "5.3.0", "from": "semver@5.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz" }, "sha": { "version": "2.0.1", "from": "sha@>=2.0.1 <2.1.0", "resolved": "https://registry.npmjs.org/sha/-/sha-2.0.1.tgz" }, "slide": { "version": "1.1.6", "from": "slide@>=1.1.6 <1.2.0", "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz" }, "sorted-object": { "version": "2.0.1", "from": "sorted-object@2.0.1", "resolved": "https://registry.npmjs.org/sorted-object/-/sorted-object-2.0.1.tgz" }, "strip-ansi": { "version": "3.0.1", "from": "strip-ansi@*", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz" }, "tar": { "version": "2.2.1", "from": "tar@>=2.2.1 <2.3.0", "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", "dependencies": { "block-stream": { "version": "0.0.8", "from": "block-stream@*", "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.8.tgz" } } }, "text-table": { "version": "0.2.0", "from": "text-table@>=0.2.0 <0.3.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz" }, "uid-number": { "version": "0.0.6", "from": "uid-number@0.0.6", "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz" }, "umask": { "version": "1.1.0", "from": "umask@>=1.1.0 <1.2.0", "resolved": "https://registry.npmjs.org/umask/-/umask-1.1.0.tgz" }, "unique-filename": { "version": "1.1.0", "from": "unique-filename@>=1.1.0 <2.0.0", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.0.tgz", "dependencies": { "unique-slug": { "version": "2.0.0", "from": "unique-slug@>=2.0.0 <3.0.0" } } }, "unpipe": { "version": "1.0.0", "from": "unpipe@>=1.0.0 <1.1.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz" }, "validate-npm-package-license": { "version": "3.0.1", "from": "validate-npm-package-license@3.0.1", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz", "dependencies": { "spdx-correct": { "version": "1.0.2", "from": "spdx-correct@>=1.0.0 <1.1.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz", "dependencies": { "spdx-license-ids": { "version": "1.2.0", "from": "spdx-license-ids@>=1.0.2 <2.0.0", "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.0.tgz" } } }, "spdx-expression-parse": { "version": "1.0.2", "from": "spdx-expression-parse@>=1.0.0 <1.1.0", "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.2.tgz", "dependencies": { "spdx-exceptions": { "version": "1.0.4", "from": "spdx-exceptions@>=1.0.4 <2.0.0", "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-1.0.4.tgz" }, "spdx-license-ids": { "version": "1.2.0", "from": "spdx-license-ids@>=1.0.0 <2.0.0" } } } } }, "validate-npm-package-name": { "version": "2.2.2", "from": "validate-npm-package-name@>=2.2.2 <2.3.0", "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-2.2.2.tgz", "dependencies": { "builtins": { "version": "0.0.7", "from": "builtins@0.0.7", "resolved": "https://registry.npmjs.org/builtins/-/builtins-0.0.7.tgz" } } }, "which": { "version": "1.2.11", "from": "which@1.2.11", "resolved": "https://registry.npmjs.org/which/-/which-1.2.11.tgz", "dependencies": { "isexe": { "version": "1.1.2", "from": "isexe@>=1.1.1 <2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-1.1.2.tgz" } } }, "wrappy": { "version": "1.0.2", "from": "wrappy@latest", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" }, "write-file-atomic": { "version": "1.2.0", "from": "write-file-atomic@1.2.0", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-1.2.0.tgz" } } }, "npmconf": { "version": "2.1.1", "from": "npmconf@2.1.1", "resolved": "https://registry.npmjs.org/npmconf/-/npmconf-2.1.1.tgz", "dev": true, "dependencies": { "once": { "version": "1.3.3", "from": "once@>=1.3.0 <1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz", "dev": true }, "semver": { "version": "4.3.6", "from": "semver@>=2.0.0 <3.0.0||>=3.0.0 <4.0.0||>=4.0.0 <5.0.0", "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz", "dev": true } } }, "number-is-nan": { "version": "1.0.1", "from": "number-is-nan@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", "dev": true }, "oauth-sign": { "version": "0.8.2", "from": "oauth-sign@>=0.8.1 <0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz" }, "object-assign": { "version": "4.1.1", "from": "object-assign@>=4.0.1 <5.0.0", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" }, "object.omit": { "version": "2.0.1", "from": "object.omit@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz" }, "on-finished": { "version": "2.3.0", "from": "on-finished@>=2.3.0 <2.4.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", "dev": true }, "once": { "version": "1.4.0", "from": "once@>=1.3.0 <2.0.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "dev": true }, "onetime": { "version": "1.1.0", "from": "onetime@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", "dev": true }, "opn": { "version": "4.0.2", "from": "opn@4.0.2", "resolved": "https://registry.npmjs.org/opn/-/opn-4.0.2.tgz" }, "optimist": { "version": "0.3.7", "from": "optimist@>=0.3.5 <0.4.0", "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.3.7.tgz", "dev": true }, "optionator": { "version": "0.8.2", "from": "optionator@>=0.8.2 <0.9.0", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", "dev": true, "dependencies": { "wordwrap": { "version": "1.0.0", "from": "wordwrap@>=1.0.0 <1.1.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", "dev": true } } }, "options": { "version": "0.0.6", "from": "options@>=0.0.5", "resolved": "https://registry.npmjs.org/options/-/options-0.0.6.tgz" }, "os-browserify": { "version": "0.2.1", "from": "os-browserify@>=0.2.0 <0.3.0", "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.2.1.tgz", "dev": true }, "os-homedir": { "version": "1.0.2", "from": "os-homedir@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", "dev": true }, "os-locale": { "version": "1.4.0", "from": "os-locale@>=1.4.0 <2.0.0", "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", "dev": true }, "os-tmpdir": { "version": "1.0.2", "from": "os-tmpdir@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz" }, "osenv": { "version": "0.1.4", "from": "osenv@>=0.1.0 <0.2.0", "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.4.tgz", "dev": true }, "package": { "version": "1.0.1", "from": "package@>=1.0.0 <1.2.0", "resolved": "https://registry.npmjs.org/package/-/package-1.0.1.tgz", "dev": true }, "pako": { "version": "0.2.9", "from": "pako@>=0.2.0 <0.3.0", "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", "dev": true }, "parse-asn1": { "version": "5.1.0", "from": "parse-asn1@>=5.0.0 <6.0.0", "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.0.tgz", "dev": true }, "parse-glob": { "version": "3.0.4", "from": "parse-glob@>=3.0.4 <4.0.0", "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz" }, "parse-json": { "version": "2.2.0", "from": "parse-json@>=2.2.0 <3.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", "dev": true }, "parseurl": { "version": "1.3.1", "from": "parseurl@>=1.3.0 <1.4.0", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.1.tgz", "dev": true }, "path-browserify": { "version": "0.0.0", "from": "path-browserify@0.0.0", "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz", "dev": true }, "path-exists": { "version": "2.1.0", "from": "path-exists@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", "dev": true }, "path-is-absolute": { "version": "1.0.1", "from": "path-is-absolute@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" }, "path-is-inside": { "version": "1.0.2", "from": "path-is-inside@>=1.0.1 <2.0.0", "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", "dev": true }, "path-parse": { "version": "1.0.5", "from": "path-parse@>=1.0.5 <2.0.0", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", "dev": true }, "path-type": { "version": "1.1.0", "from": "path-type@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", "dev": true }, "pbkdf2": { "version": "3.0.12", "from": "pbkdf2@>=3.0.3 <4.0.0", "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.12.tgz", "dev": true }, "phantomjs": { "version": "1.9.18", "from": "phantomjs@1.9.18", "resolved": "https://registry.npmjs.org/phantomjs/-/phantomjs-1.9.18.tgz", "dev": true, "dependencies": { "asn1": { "version": "0.1.11", "from": "asn1@0.1.11", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.1.11.tgz", "dev": true, "optional": true }, "assert-plus": { "version": "0.1.5", "from": "assert-plus@>=0.1.5 <0.2.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.1.5.tgz", "dev": true, "optional": true }, "async": { "version": "0.9.2", "from": "async@>=0.9.0 <0.10.0", "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", "dev": true, "optional": true }, "aws-sign2": { "version": "0.5.0", "from": "aws-sign2@>=0.5.0 <0.6.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.5.0.tgz", "dev": true, "optional": true }, "boom": { "version": "0.4.2", "from": "boom@>=0.4.0 <0.5.0", "resolved": "https://registry.npmjs.org/boom/-/boom-0.4.2.tgz", "dev": true }, "caseless": { "version": "0.6.0", "from": "caseless@>=0.6.0 <0.7.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.6.0.tgz", "dev": true }, "combined-stream": { "version": "0.0.7", "from": "combined-stream@>=0.0.4 <0.1.0", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-0.0.7.tgz", "dev": true, "optional": true }, "cryptiles": { "version": "0.2.2", "from": "cryptiles@>=0.2.0 <0.3.0", "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-0.2.2.tgz", "dev": true, "optional": true }, "delayed-stream": { "version": "0.0.5", "from": "delayed-stream@0.0.5", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-0.0.5.tgz", "dev": true, "optional": true }, "forever-agent": { "version": "0.5.2", "from": "forever-agent@>=0.5.0 <0.6.0", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.5.2.tgz", "dev": true }, "form-data": { "version": "0.1.4", "from": "form-data@>=0.1.0 <0.2.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-0.1.4.tgz", "dev": true, "optional": true }, "fs-extra": { "version": "0.23.1", "from": "fs-extra@>=0.23.1 <0.24.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.23.1.tgz", "dev": true }, "hawk": { "version": "1.1.1", "from": "hawk@1.1.1", "resolved": "https://registry.npmjs.org/hawk/-/hawk-1.1.1.tgz", "dev": true, "optional": true }, "hoek": { "version": "0.9.1", "from": "hoek@>=0.9.0 <0.10.0", "resolved": "https://registry.npmjs.org/hoek/-/hoek-0.9.1.tgz", "dev": true }, "http-signature": { "version": "0.10.1", "from": "http-signature@>=0.10.0 <0.11.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-0.10.1.tgz", "dev": true, "optional": true }, "mime-types": { "version": "1.0.2", "from": "mime-types@>=1.0.1 <1.1.0", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-1.0.2.tgz", "dev": true }, "node-uuid": { "version": "1.4.8", "from": "node-uuid@>=1.4.0 <1.5.0", "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", "dev": true }, "oauth-sign": { "version": "0.4.0", "from": "oauth-sign@>=0.4.0 <0.5.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.4.0.tgz", "dev": true, "optional": true }, "qs": { "version": "1.2.2", "from": "qs@>=1.2.0 <1.3.0", "resolved": "https://registry.npmjs.org/qs/-/qs-1.2.2.tgz", "dev": true }, "request": { "version": "2.42.0", "from": "request@2.42.0", "resolved": "https://registry.npmjs.org/request/-/request-2.42.0.tgz", "dev": true }, "sntp": { "version": "0.2.4", "from": "sntp@>=0.2.0 <0.3.0", "resolved": "https://registry.npmjs.org/sntp/-/sntp-0.2.4.tgz", "dev": true, "optional": true } } }, "pify": { "version": "2.3.0", "from": "pify@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "dev": true }, "pinkie": { "version": "2.0.4", "from": "pinkie@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz" }, "pinkie-promise": { "version": "2.0.1", "from": "pinkie-promise@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz" }, "pkg-up": { "version": "1.0.0", "from": "pkg-up@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-1.0.0.tgz", "dev": true }, "pluralize": { "version": "1.2.1", "from": "pluralize@>=1.2.1 <2.0.0", "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-1.2.1.tgz", "dev": true }, "prelude-ls": { "version": "1.1.2", "from": "prelude-ls@>=1.1.2 <1.2.0", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", "dev": true }, "preserve": { "version": "0.2.0", "from": "preserve@>=0.2.0 <0.3.0", "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz" }, "process": { "version": "0.11.10", "from": "process@>=0.11.0 <0.12.0", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", "dev": true }, "process-nextick-args": { "version": "1.0.7", "from": "process-nextick-args@>=1.0.6 <1.1.0", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz" }, "progress": { "version": "1.1.8", "from": "progress@1.1.8", "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", "dev": true }, "promise": { "version": "7.3.1", "from": "promise@>=7.1.1 <8.0.0", "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", "dev": true, "optional": true }, "proto-list": { "version": "1.2.4", "from": "proto-list@>=1.2.1 <1.3.0", "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", "dev": true }, "prr": { "version": "0.0.0", "from": "prr@>=0.0.0 <0.1.0", "resolved": "https://registry.npmjs.org/prr/-/prr-0.0.0.tgz", "dev": true }, "public-encrypt": { "version": "4.0.0", "from": "public-encrypt@>=4.0.0 <5.0.0", "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.0.tgz", "dev": true }, "punycode": { "version": "1.4.1", "from": "punycode@>=1.4.1 <2.0.0", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz" }, "q": { "version": "1.4.1", "from": "q@1.4.1", "resolved": "https://registry.npmjs.org/q/-/q-1.4.1.tgz", "dev": true }, "qs": { "version": "6.3.2", "from": "qs@>=6.3.0 <6.4.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.3.2.tgz" }, "querystring": { "version": "0.2.0", "from": "querystring@0.2.0", "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", "dev": true }, "querystring-es3": { "version": "0.2.1", "from": "querystring-es3@>=0.2.0 <0.3.0", "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", "dev": true }, "randomatic": { "version": "1.1.7", "from": "randomatic@>=1.1.3 <2.0.0", "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz", "dependencies": { "is-number": { "version": "3.0.0", "from": "is-number@>=3.0.0 <4.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", "dependencies": { "kind-of": { "version": "3.2.2", "from": "kind-of@^3.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz" } } }, "kind-of": { "version": "4.0.0", "from": "kind-of@>=4.0.0 <5.0.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz" } } }, "randombytes": { "version": "2.0.5", "from": "randombytes@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.5.tgz", "dev": true }, "raw-body": { "version": "2.1.7", "from": "raw-body@>=2.1.5 <2.2.0", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.1.7.tgz", "dev": true, "dependencies": { "bytes": { "version": "2.4.0", "from": "bytes@2.4.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.4.0.tgz", "dev": true }, "iconv-lite": { "version": "0.4.13", "from": "iconv-lite@0.4.13", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz", "dev": true } } }, "read-pkg": { "version": "1.1.0", "from": "read-pkg@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", "dev": true }, "read-pkg-up": { "version": "1.0.1", "from": "read-pkg-up@>=1.0.1 <2.0.0", "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", "dev": true }, "readable-stream": { "version": "2.3.3", "from": "readable-stream@>=2.0.2 <3.0.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz" }, "readdirp": { "version": "2.1.0", "from": "readdirp@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz" }, "readline2": { "version": "1.0.1", "from": "readline2@>=1.0.1 <2.0.0", "resolved": "https://registry.npmjs.org/readline2/-/readline2-1.0.1.tgz", "dev": true }, "rechoir": { "version": "0.6.2", "from": "rechoir@>=0.6.2 <0.7.0", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", "dev": true, "dependencies": { "resolve": { "version": "1.4.0", "from": "resolve@>=1.1.6 <2.0.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.4.0.tgz", "dev": true } } }, "regex-cache": { "version": "0.4.3", "from": "regex-cache@>=0.4.2 <0.5.0", "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.3.tgz" }, "remove-trailing-separator": { "version": "1.0.2", "from": "remove-trailing-separator@>=1.0.1 <2.0.0", "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.0.2.tgz" }, "repeat-element": { "version": "1.1.2", "from": "repeat-element@>=1.1.2 <2.0.0", "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz" }, "repeat-string": { "version": "1.6.1", "from": "repeat-string@>=1.5.2 <2.0.0", "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz" }, "request": { "version": "2.79.0", "from": "request@2.79.0", "resolved": "https://registry.npmjs.org/request/-/request-2.79.0.tgz" }, "request-progress": { "version": "0.3.1", "from": "request-progress@0.3.1", "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-0.3.1.tgz", "dev": true }, "require-directory": { "version": "2.1.1", "from": "require-directory@>=2.1.1 <3.0.0", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "dev": true }, "require-main-filename": { "version": "1.0.1", "from": "require-main-filename@>=1.0.1 <2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", "dev": true }, "require-uncached": { "version": "1.0.3", "from": "require-uncached@>=1.0.2 <2.0.0", "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", "dev": true }, "requirejs": { "version": "2.1.22", "from": "requirejs@>=2.1.0 <2.2.0", "resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.1.22.tgz", "dev": true }, "resolve": { "version": "0.3.1", "from": "resolve@>=0.3.1 <0.4.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-0.3.1.tgz", "dev": true }, "resolve-from": { "version": "1.0.1", "from": "resolve-from@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", "dev": true }, "resolve-pkg": { "version": "0.1.0", "from": "resolve-pkg@>=0.1.0 <0.2.0", "resolved": "https://registry.npmjs.org/resolve-pkg/-/resolve-pkg-0.1.0.tgz", "dev": true, "dependencies": { "resolve-from": { "version": "2.0.0", "from": "resolve-from@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", "dev": true } } }, "restore-cursor": { "version": "1.0.1", "from": "restore-cursor@>=1.0.1 <2.0.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", "dev": true }, "rewire": { "version": "1.1.2", "from": "rewire@1.1.2", "resolved": "https://registry.npmjs.org/rewire/-/rewire-1.1.2.tgz", "dev": true }, "right-align": { "version": "0.1.3", "from": "right-align@>=0.1.1 <0.2.0", "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", "dev": true }, "rimraf": { "version": "2.2.8", "from": "rimraf@>=2.2.6 <2.3.0", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz" }, "ripemd160": { "version": "2.0.1", "from": "ripemd160@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.1.tgz", "dev": true }, "run-async": { "version": "0.1.0", "from": "run-async@>=0.1.0 <0.2.0", "resolved": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz", "dev": true }, "rx-lite": { "version": "3.1.2", "from": "rx-lite@>=3.1.2 <4.0.0", "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-3.1.2.tgz", "dev": true }, "safe-buffer": { "version": "5.1.1", "from": "safe-buffer@>=5.1.1 <5.2.0", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz" }, "sax": { "version": "0.6.1", "from": "sax@>=0.6.1 <0.7.0", "resolved": "https://registry.npmjs.org/sax/-/sax-0.6.1.tgz", "dev": true }, "semver": { "version": "5.3.0", "from": "semver@5.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz" }, "set-blocking": { "version": "2.0.0", "from": "set-blocking@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "dev": true }, "set-immediate-shim": { "version": "1.0.1", "from": "set-immediate-shim@>=1.0.1 <2.0.0", "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz" }, "setimmediate": { "version": "1.0.5", "from": "setimmediate@>=1.0.4 <2.0.0", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", "dev": true }, "sha.js": { "version": "2.4.8", "from": "sha.js@>=2.4.0 <3.0.0", "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.8.tgz", "dev": true }, "shelljs": { "version": "0.7.8", "from": "shelljs@>=0.7.5 <0.8.0", "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.8.tgz", "dev": true }, "sigmund": { "version": "1.0.1", "from": "sigmund@>=1.0.0 <1.1.0", "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", "dev": true }, "slice-ansi": { "version": "0.0.4", "from": "slice-ansi@0.0.4", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", "dev": true }, "sntp": { "version": "1.0.9", "from": "sntp@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz" }, "source-list-map": { "version": "0.1.8", "from": "source-list-map@>=0.1.7 <0.2.0", "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-0.1.8.tgz", "dev": true }, "source-map": { "version": "0.5.6", "from": "source-map@>=0.5.3 <0.6.0", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", "dev": true }, "spdx-correct": { "version": "1.0.2", "from": "spdx-correct@>=1.0.0 <1.1.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz", "dev": true }, "spdx-expression-parse": { "version": "1.0.4", "from": "spdx-expression-parse@>=1.0.0 <1.1.0", "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz", "dev": true }, "spdx-license-ids": { "version": "1.2.2", "from": "spdx-license-ids@>=1.0.2 <2.0.0", "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz", "dev": true }, "sprintf-js": { "version": "1.0.3", "from": "sprintf-js@>=1.0.2 <1.1.0", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "dev": true }, "sshpk": { "version": "1.13.1", "from": "sshpk@>=1.7.0 <2.0.0", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", "dependencies": { "assert-plus": { "version": "1.0.0", "from": "assert-plus@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" } } }, "statuses": { "version": "1.3.1", "from": "statuses@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", "dev": true }, "stream-browserify": { "version": "2.0.1", "from": "stream-browserify@>=2.0.1 <3.0.0", "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz", "dev": true }, "stream-http": { "version": "2.7.2", "from": "stream-http@>=2.3.1 <3.0.0", "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.7.2.tgz", "dev": true }, "string_decoder": { "version": "1.0.3", "from": "string_decoder@>=1.0.3 <1.1.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz" }, "string-width": { "version": "1.0.2", "from": "string-width@>=1.0.1 <2.0.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "dev": true }, "stringstream": { "version": "0.0.5", "from": "stringstream@>=0.0.4 <0.1.0", "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz" }, "strip-ansi": { "version": "3.0.1", "from": "strip-ansi@>=3.0.0 <4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz" }, "strip-bom": { "version": "3.0.0", "from": "strip-bom@>=3.0.0 <4.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", "dev": true }, "strip-json-comments": { "version": "2.0.1", "from": "strip-json-comments@>=2.0.1 <2.1.0", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "dev": true }, "supports-color": { "version": "2.0.0", "from": "supports-color@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz" }, "table": { "version": "3.8.3", "from": "table@>=3.7.8 <4.0.0", "resolved": "https://registry.npmjs.org/table/-/table-3.8.3.tgz", "dev": true, "dependencies": { "ansi-regex": { "version": "3.0.0", "from": "ansi-regex@>=3.0.0 <4.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", "dev": true }, "is-fullwidth-code-point": { "version": "2.0.0", "from": "is-fullwidth-code-point@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", "dev": true }, "string-width": { "version": "2.1.1", "from": "string-width@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", "dev": true }, "strip-ansi": { "version": "4.0.0", "from": "strip-ansi@>=4.0.0 <5.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", "dev": true } } }, "tapable": { "version": "0.2.7", "from": "tapable@>=0.2.5 <0.3.0", "resolved": "https://registry.npmjs.org/tapable/-/tapable-0.2.7.tgz", "dev": true }, "tar": { "version": "2.2.1", "from": "tar@2.2.1", "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", "dev": true }, "temp": { "version": "0.8.3", "from": "temp@0.8.3", "resolved": "https://registry.npmjs.org/temp/-/temp-0.8.3.tgz" }, "temporary": { "version": "0.0.8", "from": "temporary@>=0.0.4 <0.1.0", "resolved": "https://registry.npmjs.org/temporary/-/temporary-0.0.8.tgz", "dev": true }, "text-table": { "version": "0.2.0", "from": "text-table@>=0.2.0 <0.3.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "dev": true }, "throttleit": { "version": "0.0.2", "from": "throttleit@>=0.0.2 <0.1.0", "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-0.0.2.tgz", "dev": true }, "through": { "version": "2.3.8", "from": "through@>=2.3.6 <3.0.0", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "dev": true }, "timers-browserify": { "version": "2.0.3", "from": "timers-browserify@>=2.0.2 <3.0.0", "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.3.tgz", "dev": true }, "tiny-lr": { "version": "0.2.1", "from": "tiny-lr@>=0.2.1 <0.3.0", "resolved": "https://registry.npmjs.org/tiny-lr/-/tiny-lr-0.2.1.tgz", "dev": true, "dependencies": { "qs": { "version": "5.1.0", "from": "qs@>=5.1.0 <5.2.0", "resolved": "https://registry.npmjs.org/qs/-/qs-5.1.0.tgz", "dev": true } } }, "tinycolor": { "version": "0.0.1", "from": "tinycolor@>=0.0.0 <1.0.0", "resolved": "https://registry.npmjs.org/tinycolor/-/tinycolor-0.0.1.tgz" }, "to-arraybuffer": { "version": "1.0.1", "from": "to-arraybuffer@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", "dev": true }, "touch": { "version": "0.0.3", "from": "touch@0.0.3", "resolved": "https://registry.npmjs.org/touch/-/touch-0.0.3.tgz", "dependencies": { "nopt": { "version": "1.0.10", "from": "nopt@>=1.0.10 <1.1.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz" } } }, "tough-cookie": { "version": "2.3.2", "from": "tough-cookie@>=2.3.0 <2.4.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz" }, "traverse": { "version": "0.3.9", "from": "traverse@>=0.3.0 <0.4.0", "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz" }, "tryit": { "version": "1.0.3", "from": "tryit@>=1.0.1 <2.0.0", "resolved": "https://registry.npmjs.org/tryit/-/tryit-1.0.3.tgz", "dev": true }, "tty-browserify": { "version": "0.0.0", "from": "tty-browserify@0.0.0", "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", "dev": true }, "tunnel-agent": { "version": "0.4.3", "from": "tunnel-agent@>=0.4.1 <0.5.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz" }, "tweetnacl": { "version": "0.14.5", "from": "tweetnacl@>=0.14.0 <0.15.0", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "optional": true }, "type-check": { "version": "0.3.2", "from": "type-check@>=0.3.2 <0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", "dev": true }, "type-is": { "version": "1.6.15", "from": "type-is@>=1.6.10 <1.7.0", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", "dev": true }, "typedarray": { "version": "0.0.6", "from": "typedarray@>=0.0.6 <0.0.7", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "dev": true }, "uglify-js": { "version": "2.2.5", "from": "uglify-js@>=2.2.1 <2.3.0", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.2.5.tgz", "dev": true, "dependencies": { "source-map": { "version": "0.1.43", "from": "source-map@>=0.1.7 <0.2.0", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", "dev": true } } }, "uglify-to-browserify": { "version": "1.0.2", "from": "uglify-to-browserify@>=1.0.0 <1.1.0", "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", "dev": true, "optional": true }, "uid-number": { "version": "0.0.5", "from": "uid-number@0.0.5", "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.5.tgz", "dev": true }, "underscore": { "version": "1.7.0", "from": "underscore@>=1.7.0 <1.8.0", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz", "dev": true }, "underscore.string": { "version": "2.2.1", "from": "underscore.string@>=2.2.1 <2.3.0", "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.2.1.tgz", "dev": true }, "unpipe": { "version": "1.0.0", "from": "unpipe@1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "dev": true }, "url": { "version": "0.11.0", "from": "url@>=0.11.0 <0.12.0", "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", "dev": true, "dependencies": { "punycode": { "version": "1.3.2", "from": "punycode@1.3.2", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", "dev": true } } }, "user-home": { "version": "2.0.0", "from": "user-home@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/user-home/-/user-home-2.0.0.tgz", "dev": true }, "util": { "version": "0.10.3", "from": "util@>=0.10.3 <0.11.0", "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", "dev": true, "dependencies": { "inherits": { "version": "2.0.1", "from": "inherits@2.0.1", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", "dev": true } } }, "util-deprecate": { "version": "1.0.2", "from": "util-deprecate@>=1.0.1 <1.1.0", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" }, "uuid": { "version": "3.1.0", "from": "uuid@>=3.0.0 <4.0.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz" }, "validate-npm-package-license": { "version": "3.0.1", "from": "validate-npm-package-license@>=3.0.1 <4.0.0", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz", "dev": true }, "verror": { "version": "1.3.6", "from": "verror@1.3.6", "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz" }, "vm-browserify": { "version": "0.0.4", "from": "vm-browserify@0.0.4", "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", "dev": true }, "walkdir": { "version": "0.0.11", "from": "walkdir@>=0.0.1", "resolved": "https://registry.npmjs.org/walkdir/-/walkdir-0.0.11.tgz", "dev": true }, "watchpack": { "version": "1.4.0", "from": "watchpack@>=1.2.0 <2.0.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.4.0.tgz", "dev": true, "dependencies": { "chokidar": { "version": "1.7.0", "from": "chokidar@>=1.7.0 <2.0.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", "dev": true } } }, "webpack": { "version": "2.2.1", "from": "webpack@2.2.1", "resolved": "https://registry.npmjs.org/webpack/-/webpack-2.2.1.tgz", "dev": true, "dependencies": { "acorn": { "version": "4.0.13", "from": "acorn@>=4.0.4 <5.0.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", "dev": true }, "supports-color": { "version": "3.2.3", "from": "supports-color@>=3.1.0 <4.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", "dev": true }, "uglify-js": { "version": "2.8.29", "from": "uglify-js@>=2.7.5 <3.0.0", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", "dev": true, "dependencies": { "yargs": { "version": "3.10.0", "from": "yargs@>=3.10.0 <3.11.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", "dev": true } } } } }, "webpack-sources": { "version": "0.1.5", "from": "webpack-sources@>=0.1.4 <0.2.0", "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-0.1.5.tgz", "dev": true }, "websocket-driver": { "version": "0.6.5", "from": "websocket-driver@>=0.5.1", "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.6.5.tgz", "dev": true }, "websocket-extensions": { "version": "0.1.1", "from": "websocket-extensions@>=0.1.1", "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.1.tgz", "dev": true }, "which": { "version": "1.0.9", "from": "which@>=1.0.5 <1.1.0", "resolved": "https://registry.npmjs.org/which/-/which-1.0.9.tgz", "dev": true }, "which-module": { "version": "1.0.0", "from": "which-module@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", "dev": true }, "window-size": { "version": "0.1.0", "from": "window-size@0.1.0", "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", "dev": true }, "wordwrap": { "version": "0.0.3", "from": "wordwrap@>=0.0.2 <0.1.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", "dev": true }, "wrap-ansi": { "version": "2.1.0", "from": "wrap-ansi@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "dev": true }, "wrappy": { "version": "1.0.2", "from": "wrappy@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "dev": true }, "write": { "version": "0.2.1", "from": "write@>=0.2.1 <0.3.0", "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", "dev": true }, "ws": { "version": "0.4.32", "from": "ws@>=0.4.31 <0.5.0", "resolved": "https://registry.npmjs.org/ws/-/ws-0.4.32.tgz", "dependencies": { "commander": { "version": "2.1.0", "from": "commander@>=2.1.0 <2.2.0", "resolved": "https://registry.npmjs.org/commander/-/commander-2.1.0.tgz" } } }, "xmldoc": { "version": "0.1.4", "from": "xmldoc@>=0.1.2 <0.2.0", "resolved": "https://registry.npmjs.org/xmldoc/-/xmldoc-0.1.4.tgz", "dev": true }, "xmldom": { "version": "0.1.27", "from": "xmldom@>=0.1.22 <0.2.0", "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.27.tgz", "dev": true }, "xtend": { "version": "4.0.1", "from": "xtend@>=4.0.0 <5.0.0", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz" }, "y18n": { "version": "3.2.1", "from": "y18n@>=3.2.1 <4.0.0", "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", "dev": true }, "yargs": { "version": "6.6.0", "from": "yargs@>=6.0.0 <7.0.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-6.6.0.tgz", "dev": true, "dependencies": { "camelcase": { "version": "3.0.0", "from": "camelcase@>=3.0.0 <4.0.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", "dev": true }, "cliui": { "version": "3.2.0", "from": "cliui@>=3.2.0 <4.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", "dev": true } } }, "yargs-parser": { "version": "4.2.1", "from": "yargs-parser@>=4.2.0 <5.0.0", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-4.2.1.tgz", "dev": true, "dependencies": { "camelcase": { "version": "3.0.0", "from": "camelcase@>=3.0.0 <4.0.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", "dev": true } } }, "zlib": { "version": "1.0.5", "from": "zlib@1.0.5", "resolved": "https://registry.npmjs.org/zlib/-/zlib-1.0.5.tgz", "dev": true }, "zlib-browserify": { "version": "0.0.1", "from": "zlib-browserify@0.0.1", "resolved": "https://registry.npmjs.org/zlib-browserify/-/zlib-browserify-0.0.1.tgz", "dev": true } } } ================================================ FILE: package.json ================================================ { "name": "Brackets", "version": "1.15.0-0", "apiVersion": "1.15.0", "homepage": "http://brackets.io", "issues": { "url": "http://github.com/adobe/brackets/issues" }, "repository": { "type": "git", "url": "https://github.com/adobe/brackets.git", "branch": "", "SHA": "" }, "defaultExtensions": { "brackets-eslint": "3.2.0" }, "dependencies": { "anymatch": "1.3.0", "async": "2.1.4", "chokidar": "1.6.1", "decompress-zip": "0.3.0", "fs-extra": "2.0.0", "lodash": "4.17.15", "npm": "3.10.10", "opn": "4.0.2", "request": "2.79.0", "semver": "5.3.0", "temp": "0.8.3", "ws": "~0.4.31" }, "devDependencies": { "glob": "7.1.1", "grunt": "0.4.5", "husky": "0.13.2", "jasmine-node": "1.11.0", "grunt-jasmine-node": "0.1.0", "grunt-cli": "0.1.9", "phantomjs": "1.9.18", "grunt-lib-phantomjs": "0.3.0", "grunt-eslint": "19.0.0", "grunt-contrib-watch": "1.0.0", "grunt-contrib-jasmine": "0.4.2", "grunt-template-jasmine-requirejs": "0.1.0", "grunt-contrib-cssmin": "0.6.0", "grunt-contrib-clean": "0.4.1", "grunt-contrib-copy": "0.4.1", "grunt-contrib-htmlmin": "0.1.3", "grunt-contrib-less": "1.4.0", "grunt-contrib-requirejs": "0.4.1", "grunt-contrib-uglify": "0.2.0", "grunt-contrib-concat": "0.3.0", "grunt-targethtml": "0.2.6", "grunt-usemin": "0.1.11", "grunt-cleanempty": "1.0.3", "load-grunt-tasks": "3.5.0", "q": "1.4.1", "rewire": "1.1.2", "tar": "2.2.1", "webpack": "2.2.1", "xmldoc": "0.1.2", "zlib": "1.0.5" }, "scripts": { "prepush": "npm run eslint", "postinstall": "grunt install", "test": "grunt test", "eslint": "grunt eslint" }, "licenses": [ { "type": "MIT", "url": "https://github.com/adobe/brackets/blob/master/LICENSE" } ] } ================================================ FILE: samples/bg/Getting Started/index.html ================================================ ПЪРВИ СТЪПКИ С BRACKETS

ПЪРВИ СТЪПКИ С BRACKETS

Това е Вашето ръководство!

Добре дошли в Brackets, модерен редактор за код с отворен код, който разбира от уеб дизайн. Това е лек, но мощен редактор за код, който слива визуалните инструменти с редактора, така че да получавате достатъчно помощ, когато сте в нужда.

Brackets е един различен вид редактор. Brackets има някои уникални функционалности, като бързо редактиране, преглед на живо и други, които може да не откриете в други редактори. Brackets е написан на JavaScript, HTML и CSS. Това означава, че повечето от хората, които използват Brackets имат уменията и да го променят и подобряват. Всъщност, ние самите използваме Brackets всеки ден, за да градим Brackets. За да научите повече относно ключовите функционалности, продължавайте да четете.

Проектите в Brackets

За да редактирате своя код в Brackets, трябва да отворите папката, съдържаща файловете Ви. Brackets смята текущо отворената папка за „проект“; функционалностите като подсказки за кода, преглед на живо и бързо редактиране работят само с файловете в текущо отворената папка.

След като приключите с разглеждането на този примерен проект и искате да редактирате собствен код, можете да използвате падащото меню в лявата странична лента, за да превключвате между папките. В момента там е избрано „Getting Started“ — това е папката, съдържаща файла, който разглеждате в момента. Щракнете върху падащото меню и изберете „Отваряне на папка…“, за да отворите своя собствена папка. Можете да използвате това падащо меню, за да се връщате обратно към папките, които сте отворили по-рано, включително този примерен проект.

Бързо редактиране на CSS и JavaScript

Без повече превключване между документи и забравяне къде сте били последно. Когато редактирате HTML, използвайте комбинацията Cmd/Ctrl + E, за да отворите бърз редактор на място, който показва използвания CSS. Променете CSS кода, натиснете ESC и се връщате към редактирането на HTML, или просто оставете CSS правилата отворени и те ще станат част от редактора Ви за HTML. Ако натиснете ESC извън вмъкнатия бърз редактор, всички такива ще се скрият. Бързото редактиране разпознава и правила, описани чрез LESS и SCSS, включително и вложени правила.

Искате да го видите в действие? Поставете курсора върху елемента по-горе и натиснете Cmd/Ctrl + E. Би трябвало да се появи бърз редактор за CSS, показващ CSS правилото, което се прилага към този елемент. Бързото редактиране може да работи също и за класове и идентификатори. Можете дори да го използвате с Вашите файлове с LESS и SCSS. Можете да създавате правила по същия начин. Щракнете върху един от елементите по-гори и натиснете Cmd/Ctrl + E. В момента няма правила за тях, но можете да натиснете бутона „Ново правило“ и да добавите ново правило за . Снимка на екрана, показваща бързото редактиране на CSS

Можете да използвате същата клавишна комбинация, за да редактирате и други неща — например функции на JavaScript, цветове и времеви функции за анимации; ние постоянно добавяме още.

За сега редакторите на място не могат да се влагат един в друг, така че можете да използвате бързото редактиране само когато курсорът е в „пълния“ редактор.

Преглеждайте промените в HTML и CSS на живо в браузъра

Нали знаете как години наред си играем на „запазване и презареждане“? Играта, в която правите промяна в редактора си, натискате „запазване“, превключвате към браузъра и натискате „презареждане“, за да видите резултата? С Brackets, няма да ви се налага да я играете повече.

Brackets ще отвори жива връзка с браузъра Ви и ще му праща промените в HTML и CSS кода докато пишете! Може би вече правите нещо подобно с различни инструменти, работещи в браузъра, но с Brackets няма да има нужда да копирате готовия код обратно в редактора. Кодът Ви работи в браузъра, но живее в редактора!

Осветява на HTML елементи и CSS правила на живо

С Brackets е лесно да видите как промените Ви в HTML и CSS ще променят страницата. Когато курсорът е върху CSS правило, Brackets ще осветява всички елементи, върху които то влияе, в браузъра. Също така, докато редактирате файл с HTML, Brackets ще осветява съответстващите елементи в браузъра.

Ако имате Google Chrome, може да опитате това. Щракнете иконката на светкавица в горния десен ъгъл на прозореца на Brackets или натиснете Cmd/Ctrl + Alt + P. Когато прегледът на живо е включен за даден HTML документ, всички свързани CSS документи могат да бъдат редактирани в реално време. Иконката ще смени цвета си от сив на златист, когато Brackets установи връзка с браузъра Ви. Сега поставете курсора си върху елемента по-горе. Забележете синия контур, който се появява около изображението в Chrome. Сега натиснете Cmd/Ctrl + E, за да отворите CSS правилата. Опитайте да промените размера на рамката от 10 на 20 пиксела или променете цвета на фона от „transparent“ на „hotpink“. Ако Brackets и браузърът Ви работят един до друг, ще видите как промените Ви автоматично се виждат в браузъра. Яко, нали?

Засега Brackets поддържа преглед на живо само за HTML и CSS. И все пак, в текущото издание, промените във файловете с код на JavaScript се презареждат автоматично при запазване на файла. В момента работим върху поддръжката на преглед на живо и за JavaScript. Прегледът на живо работи само с Google Chrome, но се надяваме да поддържаме всички основни браузъри в бъдеще.

Бърз преглед

Онези от нас, които не могат да запомнят съответствието между цветовете, изразени чрез шестнадесетични числа и стойности ЧЗС, Brackets прави лесна и бърза проверката на това кой цвят се използва. Както в CSS, така и в HTML, можете просто да посочите дадена цветова стойност или преливка, и Brackets ще Ви покаже как изглежда този цвят или преливка автоматично. Същото важи и за изображенията: просто посочете връзката към изображението в редактора и ще се появи миниатюрен изглед на това изображение.

За да опитате бързия преглед сами, поставете курсора върху елемента в началото на този документ и натиснете Cmd/Ctrl + E, за да отворите бързия редактор на CSS. Сега просто посочете някоя стойност за цвят. Можете да видите това и при преливките, като отворите бърз редактор за елемента и посочите някоя от стойностите за фона. За да опитате прегледа на изображения, поставете курсора върху снимката на екрана, която може да намерите по-нагоре в този документ.

Имате нужда от повече? Опитайте някое разширение!

Освен всички приятни функционалности, вградени в Brackets, нашата огромна и постоянно нарастваща общност от разработчици на разширения е създала стотици такива, които добавят полезни и удобни функционалности. Ако има нещо, от което се нуждаете, но не намирате в Brackets, много вероятно е някой вече да е създал разширение за това. За да разгледате или претърсите списъка от налични разширения натиснете Файл > Управител на разширения… и изберете раздела „Налични“. Когато намерите това, което искате, просто натиснете бутона „Инсталиране“ до него.

Включете се

Brackets е проект с отворен код. Разработчици от цял свят допринасят, за да изградим заедно един по-добър редактор за код. Много други създават разширения, които увеличават възможностите на Brackets. Кажете ни какво мислите, споделете идеите си или направо се включете в проекта!

================================================ FILE: samples/bg/Getting Started/main.css ================================================ html { background: #e6e9e9; background-image: linear-gradient(270deg, rgb(230, 233, 233) 0%, rgb(216, 221, 221) 100%); -webkit-font-smoothing: antialiased; } body { background: #fff; box-shadow: 0 0 2px rgba(0, 0, 0, 0.06); color: #545454; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 16px; line-height: 1.5; margin: 0 auto; max-width: 800px; padding: 2em 2em 4em; } h1, h2, h3, h4, h5, h6 { color: #222; font-weight: 600; line-height: 1.3; } h2 { margin-top: 1.3em; } a { color: #0083e8; } b, strong { font-weight: 600; } samp { display: none; } img { animation: colorize 2s cubic-bezier(0, 0, .78, .36) 1; background: transparent; border: 10px solid rgba(0, 0, 0, 0.12); border-radius: 4px; display: block; margin: 1.3em auto; max-width: 95%; } @keyframes colorize { 0% { -webkit-filter: grayscale(100%); filter: grayscale(100%); } 100% { -webkit-filter: grayscale(0%); filter: grayscale(0%); } } ================================================ FILE: samples/cs/Getting Started/index.html ================================================ ZAČÍNÁME S BRACKETS

ZAČÍNÁME S BRACKETS

Tohle je váš průvodce!

Vítejte v Brackets, moderním open-source editoru kódu, který rozumí webdesignu. Odlehčeném, a přesto výkonném editoru kódu, který prolíná textový editor s vizuálními nástroji, takže dostanete správné množství pomoci, kdy budete chtít.

Brackets je jiný druh editoru. Brackets obsahuje některé unikátní prvky, jako rychlou úpravu, živý náhled a další, které v jiných editorech pravděpodobně nenajdete. Navíc je Brackets napsán v JavaScriptu, HTML a CSS. To znamená, že mnoho z nás používajících Brackets má znalosti potřebné k úpravě nebo rozšíření editoru. My sami používáme Brackets neustále k vývoji Brackets. Čtěte dál, pokud se chcete dozvědět více o tom, jak používat některé klíčové funkce editoru.

Projekty v Brackets

Abyste mohli editovat vlastní kód pomocí Brackets, stačí jenom otevřít složku obsahující vaše soubory. Brackets považuje aktuálně otevřenou složku za „projekt“; funkce, jako např. nápovědy kódu, živý náhled nebo rychlou úpravu, pak používá jenom u souborů uvnitř aktuálně otevřené složky.

Jakmile budete připraveni odejít z tohoto ukázkového projektu a editovat vlastní kód, můžete použít rozbalovací nabídku v levém bočním panelu ke změně složek. Rozbalovací nabídka právě teď ukazuje „Getting Started“ - to je složka obsahující soubor, který právě teď prohlížíte. Klikněte na rozbalovací nabídku a vyberte „Otevřít složku…“ k otevření vaší vlastní složky. Rozbalovací nabídku můžete použít také později k přechodu zpátky do složek, které jste otevřeli dříve, včetně tohoto ukázkového projektu.

Rychlá úprava pro CSS a JavaScript

Žádné další přepínání mezi dokumenty a ztrácení souvislostí. Když editujete HTML, použijte klávesovou zkratku Cmd/Ctrl + E k otevření rychle vloženého editoru, který zobrazí veškeré související CSS. Proveďte drobnou úpravu ve vašem CSS, stiskněte ESC a jste zpátky v editaci HTML, nebo prostě nechte CSS předpisy otevřené, čímž se stanou součástí vašeho HTML editoru. Pokud stisknete ESC mimo rychle vložený editor, skryjí se tyto editory všechny. Rychlá úprava najde také předpisy definované v LESS a SCSS souborech, včetně těch vnořených.

Chcete to vidět v akci? Umístěte kurzor na značku výše a stiskněte Cmd/Ctrl + E. Pod danou značkou by se měla objevit rychlá úprava CSS, zobrazující související CSS předpis. Rychlá úprava funguje také v atributech class a id. Stejně tak ji můžete využít ve vašich LESS a SCSS souborech. Stejným způsobem můžete vytvořit i předpisy nové. Klikněte na jednu ze značek výše a stiskněte Cmd/Ctrl + E. Zatím tu žádné předpisy nejsou, ale můžete kliknout na tlačítko Nový předpis, čímž přidáte nový předpis pro . Snímek obrazovky zobrazující rychlou úpravu CSS

Stejnou klávesovou zkratku můžete použít i k editaci jiných věcí - např. funkcí v JavaScriptu, barev nebo funkcí pro načasování animací - a pořád přidáváme další a další.

Vložené editory prozatím nemohou být vnořené, rychlou úpravu tedy můžete použít pouze pokud je kurzor uvnitř „plnohodnotného“ editoru.

Zobrazte změny v HTML a CSS živě v prohlížeči

Znáte ten tanec „uložitaobnovit“, který předvádíme řadu let? Takový ten, kdy provedete změny ve vašem editoru, uložíte je, přepnete na prohlížeč a obnovíte stránku, abyste nakonec viděli výsledek? S Brackets se tomuhle tanci můžete vyhnout.

Brackets otevře živé spojení s vaším prohlížečem a posílá změny v HTML a CSS během psaní! Možná už dnes děláte něco podobného s nástroji v prohlížečích, ale s Brackets není potřeba kopírovat výsledný kód a vkládat jej zpátky do editoru. Váš kód běží uvnitř prohlížeče, ale žije ve vašem editoru!

Živé zvýraznění HTML prvků a CSS předpisů

Díky Brackets jednoduše uvidíte, jak vaše změny v HTML a CSS ovlivní stránku. Pokud umístíte kurzor na CSS předpis, Brackets zvýrazní všechny zasažené prvky v prohlížeči. Podobně i při editaci HTML souboru Brackets zvýrazní odpovídající HTML prvky v prohlížeči.

Pokud máte nainstalovaný Google Chrome, můžete si to vyzkoušet sami. Klikněte na ikonu blesku v pravém horním rohu vašeho okna Brackets nebo stiskněte Cmd/Ctrl + Alt + P. Pokud je pro HTML dokumenty povolen živý náhled, veškeré připojené CSS dokumenty mohou být editovány v reálném čase. Ikona se změní z šedé na zlatou pokud Brackets naváže spojení s vaším prohlížečem. Nyní umístěte kurzor na značku výše. Všimněte si modrého zvýraznění, které se objeví v Google Chrome kolem obrázku. Dále použijte Cmd/Ctrl + E k otevření definovaných CSS předpisů. Zkuste změnit šířku rámečku z 10px na 20px nebo změnit barvu pozadí z „transparent“ na „hotpink“. Pokud běží Brackets a váš prohlížeč vedle sebe, uvidíte, jak se vaše změny okamžitě projeví ve vašem prohlížeči. Úžasné, že?

Brackets v současnosti podporuje živý náhled pouze pro HTML a CSS. V aktuální verzi jsou změny v JavaScriptových souborech alespoň automaticky načteny, jakmile je uložíte. Aktuálně pracujeme právě na podpoře živého náhledu pro JavaScript. Živé náhledy jsou také možné jenom s prohlížečem Google Chrome, ale doufáme, že tuto funkci v budoucnu přineseme do všech hlavních prohlížečů.

Rychlý náhled

Pro ty z vás, kteří si ještě nezapamatovali ekvivalenty barev pro HEX nebo RGB hodnoty, Brackets rychle a jednoduše zobrazí, jaká barva je právě používána. Jak v CSS, tak v HTML prostě najeďte na jakoukoliv barevnou hodnotu nebo barevný přechod a Brackets automaticky zobrazí náhled dané barvy nebo daného barevného přechodu. To samé platí pro obrázky: jednoduše najeďte na odkaz obrázku v editoru Brackets a ten zobrazí malý náhled daného obrázku.

Pokud si rychlý náhled chcete vyzkoušet sami, umístěte kurzor na značku výše v tomto dokumentu a stiskněte Cmd/Ctrl + E k otevření rychlého editoru CSS. Nyní jednoduše najeďte na kteroukoliv barevnou hodnotu v CSS. Také náhled barevných přechodů můžete vidět v akci otevřením rychlého editoru CSS na značce a najetím na kteroukoliv hodnotu background-image. K vyzkoušení náhledu obrázku umístěte kurzor na snímek obrazovky vložený výše v tomto dokumentu.

Potřebujete něco jiného? Zkuste doplněk!

Navíc ke všemu skvělému, co je zabudované do Brackets, ještě naše rozsáhlá a rostoucí komunita vývojářů doplňků vyvinula stovky doplňků přidávající další užitečné funkce. Pokud je tu něco, co potřebujete, ale Brackets to nenabízí, s největší pravděpodobností už pro to někdo vytvořil doplněk. K procházení nebo prohledání seznamu dostupných doplňků vyberte Soubor > Správce doplňků… a klikněte na záložku „Dostupné“. Až naleznete doplněk, který hledáte, prostě klikněte na tlačítko „Instalovat“ vedle něj.

Zapojte se

Brackets je open-source projekt. Weboví vývojáři z celého světa se podílejí na vývoji a vylepšování editoru. Mnoho dalších vyvíjí doplňky, které rozšiřují možnosti Brackets. Dejte nám vědět, co si myslíte, sdílejte své nápady nebo se přímo podílejte na projektu.

================================================ FILE: samples/cs/Getting Started/main.css ================================================ html { background: #e6e9e9; background-image: linear-gradient(270deg, rgb(230, 233, 233) 0%, rgb(216, 221, 221) 100%); -webkit-font-smoothing: antialiased; } body { background: #fff; box-shadow: 0 0 2px rgba(0, 0, 0, 0.06); color: #545454; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 16px; line-height: 1.5; margin: 0 auto; max-width: 800px; padding: 2em 2em 4em; } h1, h2, h3, h4, h5, h6 { color: #222; font-weight: 600; line-height: 1.3; } h2 { margin-top: 1.3em; } a { color: #0083e8; } b, strong { font-weight: 600; } samp { display: none; } img { animation: colorize 2s cubic-bezier(0, 0, .78, .36) 1; background: transparent; border: 10px solid rgba(0, 0, 0, 0.12); border-radius: 4px; display: block; margin: 1.3em auto; max-width: 95%; } @keyframes colorize { 0% { -webkit-filter: grayscale(100%); filter: grayscale(100%); } 100% { -webkit-filter: grayscale(0%); filter: grayscale(0%); } } ================================================ FILE: samples/da/Kom godt i gang/index.html ================================================ KOM GODT I GANG MED BRACKETS

KOM GODT I GANG MED BRACKETS

Dette er din guide!

Velkommen til dette tidlige smugkig på Brackets, en ny open-source editor til den næste generation af nettet. Vi er stærke tilhængere af standarder og ønsker at skabe bedre værktøjer til JavaScript, HTML, CSS og andre åbne web teknologier. Dette er vores ydmyge begyndelse.

Brackets er en anderledes editor. En nævneværdig forskel er, at denne editor er skrevet i JavaScript, HTML og CSS. Det betyder, at de fleste af jer der bruger Brackets har de nødvendige færdigheder til at ændre og udvide editoren. Faktisk bruger vi selv Brackets hver dag for at bygge Brackets. Det har også nogle unikke funktioner såsom Lyn-Redigering, Live-Forhåndsvisning og andre som du ikke finder i andre editorer. Hvis du vil vide mere om, hvordan du bruger disse funktioner, så læs videre.

Vi afprøver et par nye ting

Lyn-Redigering til CSS og JavaScript

Slut med at skifte mellem dokumenter og miste fokuset. Når du redigerer HTML, kan du trykke Cmd/Ctrl + E for at åbne en indlejret editor som viser alt det relevante CSS. Tilpas dit CSS, tryk ESC og du ryger tilbage til dit HTML-dokument, eller du kan efterlade CSS-reglerne åbne så de bliver en del af din HTML-editor. Hvis du trykker ESC udenfor en Lyn-Redigerings-editor, bliver de alle klappet sammen.

Vil du se hvordan det virker? Placér markøren på tagget ovenfor og tryk Cmd/Ctrl + E. Du burde se Lyn-Redigering dukke frem, som viser den CSS-regel som anvendes på den. Lyn-Redigering virker også med klasse- og id-attributter. Du kan oprette nye regler på samme måde. Klik i en af tagsne ovenover og tryk Cmd/Ctrl + E. Der er ingen regler til den lige nu, men du kan klikke på "Ny Regel" for at oprette en ny regel til . Et skærmbillede der viser CSS-Lyn-Redigering

Du kan også bruge den samme genvej til at redigere andre ting--såsom funktioner i JavaScript, farver, og timing-funktioner til animationer--og vi føjer mere til hele tiden. Indtil videre kan editorerne ikke indlejres i hinanden, så du kan kun bruge Lyn-Redigering så længe markøren er i det primære redigerings-felt.

Se ændringer i HTML og CSS live i browseren

Kender du den "gem/genindlæs"-finte vi har lavet i årevis? Den hvor du laver ændringer i din editor, gemmer, skifter over til browseren og så genindlæser for endeligt at se resultatet? Med Brackets kan du lægge den finte på hylden.

Brackets åbner en direkte forbindelse til din lokale browser og sender HTML og CSS opdateringer imens du skriver! Måske gør du allerede noget lignende i dag med browser-baserede værktøjer, men med Brackets behøver du ikke kopiere den endelige kode tilbage i editoren. Din kode kører i din browser, men bor i din editor!

Live fremhævelse af HTML-elementer og CSS-regler

Brackets gør det nemt at se dine ændringer i HTML og CSS vil påvirke siden. Når markøren er på en CSS-regel, vil Brackets fremhæve alle påvirkede elementer i browseren. Ligeledes, når en HTML-fil redigeres, vil Brackets fremhæve de tilsvarende HTML-elementer i browseren.

Hvis du har Google Chrome installeret, kan du prøve det af selv. Klik på lyn-ikonet i øverste højre hjørne af Brackets vinduet eller tryk Cmd/Ctrl + Alt + P. Når Live-Forhåndsvisning slåes til på et HTML-dokument, kan alle tilknyttede CSS-dokumenter redigeres i realtid. Ikonet skifter fra grå til guld når Brackets har etableret en forbindelse til browseren. Placér nu markøren på tagget ovenover. Bemærk den blå fremhævning der dukker op rundt om billedet i Chrome. Tryk derefter på Cmd/Ctrl + E for at åbne de definerede CSS-regler. Prøv at ændre tykkelsen på kanten fra 10px til 20px eller ændre baggrundsfarven fra "transparent" til "hotpink". Hvis du har Brackets og browseren til at køre side om side, kan du med det samme se dine ændringer blive vist i browseren. Er det ikke sejt?

I dag understøtter Brackets kun Live-Forhåndsvisning for HTML og CSS. Ændringer til JavaScript-filer bliver dog genindlæst automatisk når du gemmer. Vi arbejder i øjeblikket på at Live-Forhåndsvisning også understøtter JavaScript. Live-Forhåndsvisning er også kun muligt med Google Chrome, men vi håber på at bringe denne funktionalitet ud til alle gængse browsere i fremtiden.

Lyn-visning

For dem af os, som endnu ikke kan alle farvers HEX- eller RGB-kode udenad, gør Brackets det hurtigt og nemt at se nøjagtig hvilken farve der er brugt. I enten CSS eller HTML, peger du ganske enkelt på en farve-værdi eller gradient og Brackets vil vise et eksempel af den farve/gradient automatisk. Det samme gælder for billeder: du peger ganske enkelt på billede-adressen i editoren og der vises en miniature-udgave af det billede.

Du kan afprøve Lyn-Visning ved at placére markøren på tagget øverst i dette dokument og trykke Cmd/Ctrl + E for at åbne CSS-Lyn-Redigering. Her kan du pege på enhver farve-værdi i CSS'en og se farven. Du kan også set det i aktion i gradienter ved at åbne Lyn-Redigering på tagget og pege på en af værdierne for baggrundsbilledet. For at se et smugkig af billeder, peg på adressen til skærmbilledet, som er indsat tidligere i dette dokument.

Har du brug for noget andet? Prøv en udvidelse!

Som tilføjelse til alle de gode sager der er indbygget i Brackets, har vores store og voksende samfund af udviklere skabt over hundrede udvidelser, som tilføjer nyttig funktionalitet. Hvis der er noget du har brug for, som Brackets ikke tilbyder, er det ret sandsynligt at nogen har lavet en udvidelse til det. For at gennemse eller søge i listen af tilgængelige udvidelser, vælg Filer > Udvidelses-håndtering og klik på fanen "Udvalg". Når du har fundet en udvidelse du kunne tænke dig, klikker du blot på knappen "Installér" ud for den.

Bliv involveret

Brackets er et open-source projekt. Web-udviklere fra hele verden bidrager til at bygge en bedre kode-editor. Endnu flere bygger udvidelser der udvider funktionaliteten af Brackets. Fortæl os hvad du synes, del dine idéer eller bidrag direkte til projektet.

================================================ FILE: samples/da/Kom godt i gang/main.css ================================================ html { background: #e6e9e9; background-image: linear-gradient(270deg, rgb(230, 233, 233) 0%, rgb(216, 221, 221) 100%); -webkit-font-smoothing: antialiased; } body { background: #fff; box-shadow: 0 0 2px rgba(0, 0, 0, 0.06); color: #545454; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 16px; line-height: 1.5; margin: 0 auto; max-width: 800px; padding: 2em 2em 4em; } h1, h2, h3, h4, h5, h6 { color: #222; font-weight: 600; line-height: 1.3; } h2 { margin-top: 1.3em; } a { color: #0083e8; } b, strong { font-weight: 600; } samp { display: none; } img { animation: colorize 2s cubic-bezier(0, 0, .78, .36) 1; background: transparent; border: 10px solid rgba(0, 0, 0, 0.12); border-radius: 4px; display: block; margin: 1.3em auto; max-width: 95%; } @keyframes colorize { 0% { -webkit-filter: grayscale(100%); filter: grayscale(100%); } 100% { -webkit-filter: grayscale(0%); filter: grayscale(0%); } } ================================================ FILE: samples/de/Erste Schritte/index.html ================================================ ERSTE SCHRITTE MIT BRACKETS

ERSTE SCHRITTE MIT BRACKETS

Dies ist Ihre Anleitung!

Willkommen zu Brackets, einem modernen, quelloffenen Code-Editor, der Webdesign versteht. Es ist ein einfacher, aber dennoch leistungsfähiger Editor, der Ihnen immer die richtigen Tools einblendet, sodass Sie die genau richtige Menge an Hilfestellung haben, wann immer Sie diese brauchen.

Brackets ist eine andere Art Editor. Brackets hat ein paar einzigartige Features wie Schnelles Bearbeiten, Live-Vorschau und zahlreiche weitere, die Sie in anderen Editoren vergeblich suchen werden. Zudem ist Brackets in JavaScript, HTML und CSS geschrieben. Das heißt, dass die meisten Brackets-Nutzer dazu in der Lage sind, den Editor selbst zu verändern und zu erweitern. Tatsächlich nutzen wir Brackets täglich, um Brackets zu verbessern. Lesen Sie weiter, um mehr über die Nutzung der Hauptfeatures zu erfahren.

Projekte in Brackets

Um Ihren eigenen Code in Brackets zu bearbeiten, können Sie einfach den Ordner öffnen, der Ihre Dateien enthält. Brackets sieht den geöffneten Ordner als "Projekt"; Features wie Code-Vervollständigung, Live-Vorschau und Schnelles Bearbeiten nutzen nur Dateien im aktuell geöffneten Ordner.

Sobald Sie bereit sind, dieses Beispielprojekt zu verlassen und Ihren eigenen Code zu editieren, können Sie die Drop-Down-Liste auf der linken Seite nutzen, um einen Ordner auszuwählen. Die Drop-Down-Liste heißt zurzeit "Erste Schritte" - das ist der Ordner, der die aktuell geöffnete Datei enthält. Klicken Sie darauf und wählen Sie daraufhin "Ordner öffnen…", um Ihren eigenen Ordner zu öffnen. Sie können auf diese Weise auch später wieder zu zuvor geöffneten Ordnern, wie diesem Beispielprojekt, zurückkehren.

Schnelles Bearbeiten von CSS und JavaScript

Kein Wechsel zwischen Dokumenten mehr - so verlieren Sie nie den Überblick. Wenn Sie HTML editieren, können Sie die Tastenkombination Cmd/Strg + E verwenden, um einen Inline-Editor anzuzeigen, der Ihnen alle relevanten CSS-Regeln zum Schnellen Bearbeiten anzeigt. Ändern Sie etwas im CSS, drücken Sie ESC und schon sind Sie zurück im HTML-Code. Oder lassen Sie die CSS-Regeln einfach offen und sie werden Teil ihres HTML-Editors. Sobald Sie ESC außerhalb eines solchen Editors drücken, schließen sich alle zusammen. Ein Inline-Editor zeigt Ihnen auch Regeln in LESS- und SCSS-Dateien, inklusive geschachtelter Regeln.

Sie wollen das in Aktion sehen? Setzen Sie Ihren Cursor auf den -Tag oben und drücken Sie Cmd/Strg + E. Sie sollten einen Editor zum Schnellen Bearbeiten von CSS erscheinen sehen, der die geltenden CSS-Regeln anzeigt. Das Schnelle Bearbeiten funktioniert genauso in Klassen- und ID-Attributen. Sie können es zudem in LESS- und SCSS-Dateien nutzen. Sie können auf die selbe Weise neue Regeln erstellen. Klicken Sie in einen der -Tags weiter oben und drücken Sie Cmd/Strg + E. Es gibt noch keine Regeln dafür, aber Sie können den "Neue Regel"-Button nutzen, um eine neue Regel für hinzuzufügen. Ein Screenshot, der Schnelles Bearbeiten von CSS zeigt

Sie können die selbe Tastenkombination nutzen, um andere Dinge auf die selbe Weise zu bearbeiten - wie JavaScript-Funktionen, Farben und Animations-Timing-Funktionen - und wir fügen ständig mehr hinzu.

Im Augenblick können solche Editoren allerdings nicht verschachtelt werden. Sie können das Schnelle Bearbeiten also nur nutzen, während der Cursor sich im Haupteditor befindet.

Vorschau auf HTML- und CSS-Änderungen live im Browser anzeigen

Sie kennen den "Speichern/Neu laden-Tanz", den wir seit Jahren aufführen? Der, in dem Sie Änderungen in Ihrem Editor machen, Speichern drücken, zum Browser schalten und dann neu laden, um schließlich das Ergebnis zu sehen? Mit Brackets müssen Sie diesen Tanz nicht aufführen.

Brackets öffnet eine Live-Verbindung zu Ihrem lokalen Browser und sendet HTML- und CSS-Updates, während Sie tippen! Eventuell tun Sie etwas Ähnliches bereits heute mit browserbasierten Tools, doch mit Brackets ist kein Kopieren und Einfügen des endgültigen Codes im Editor mehr nötig. Ihr Code läuft im Browser, aber lebt in Ihrem Editor!

Hervorheben von HTML-Elementen und CSS-Regeln - live!

Brackets macht es Ihnen leicht, zu sehen, welche Auswirkungen Ihre Änderungen in HTML und CSS auf die Seite haben werden. Wenn Ihr Cursor auf einer CSS-Regel platziert ist, hebt Brackets alle zugehörigen Elemente im Browser hervor. Genauso wird Brackets beim Editieren einer HTML-Datei die entsprechenden HTML-Elemente im Browser markieren.

Falls Sie Google Chrome installiert haben, können Sie das selbst ausprobieren. Klicken Sie auf das Blitz-Symbol oben rechts oder drücken Sie Cmd/Strg + Alt + P. Wenn die Live-Vorschau für ein HTML-Dokument aktiviert ist, können alle verknüpften CSS-Dokumente in Echtzeit bearbeitet werden. Das Symbol ändert die Farbe von grau nach gold, sobald Brackets eine Verbindung zu Ihrem Browser hergestellt hat. Platzieren Sie Ihren Cursor jetzt auf dem -Tag oben. Sie sehen in Chrome eine blaue Markierung, die um das Bild herum erscheint. Nutzen Sie nun Cmd/Strg + E, um die definierten CSS-Regeln anzuzeigen. Probieren Sie, die Stärke des Rahmens von 10px auf 20px zu ändern, oder ändern Sie die Hintergrundfarbe von "transparent" zu "hotpink". Falls Sie Brackets und Ihren Browser nebeneinander laufen haben, können Sie die Änderungen sofort in Ihrem Browser erkennen. Cool, was?

Derzeit unterstützt Brackets die Live-Vorschau nur für HTML und CSS. Allerdings werden in der aktuellen Version Änderungen an JavaScript-Dateien automatisch neu geladen, wenn Sie diese speichern. Wir arbeiten momentan an der Unterstützung der Live-Vorschau für JavaScript. Die Live-Vorschau ist außerdem nur mit Google Chrome möglich, doch wir hoffen, diese Funktionalität zukünftig zu allen wichtigen Browsern hinzuzufügen.

Schnelle Farbansicht

Für die unter uns, die immer noch nicht die Farb-Äquivalente von HEX- und RGB-Werten kennen, macht es Brackets einfach und schnell, exakt zu sehen, welche Farbe genutzt wird. Fahren Sie in HTML oder CSS einfach über einen Farbwert oder -verlauf und Brackets wird Ihnen automatisch eine Vorschau davon anzeigen. Das selbe gilt für Bilder: Platzieren Sie den Cursor im Brackets-Editor über einem Link zu einem Bild und er wird ein Miniaturansicht von diesem Bild zeigen.

Um die Schnelle Farbansicht selbst auszuprobieren, können Sie Ihren Cursor auf dem -Tag am Anfang dieses Dokuments platzieren und Cmd/Strg + E drücken, um einen CSS-Schnell-Editor zu öffnen. Fahren Sie nun einfach mit dem Cursor über einen der Farbwerte im CSS-Code. Sie können das auch mit Farbverläufen sehen, wenn Sie einen Schnell-Editor für den -Tag anzeigen lassen und über irgendeinen der "background-image"-Farbwerte fahren. Um die Bildvorschau auszuprobieren, können Sie Ihren Cursor auf dem Screenshot-Link platzieren, den Sie weiter oben in diesem Dokument finden.

Sie benötigen etwas anderes? Probieren Sie es mit einer Erweiterung!

Zusätzlich zu all dem, was in Brackets eingebaut ist, hat unsere große und wachsende Community der Erweiterungs-Entwickler hunderte Erweiterungen erstellt, die nützliche Funktionen bringen. Wenn Sie etwas brauchen, was es nicht in Brackets gibt, ist es sehr wahrscheinlich, dass bereits jemand eine Erweiterung dafür geschrieben hat. Um die Liste der verfügbaren Erweiterungen zu durchstöbern oder zu durchsuchen nutzen Sie Datei > Erweiterungs-Verwaltung und klicken auf den Tab "Verfügbar". Wenn Sie eine Erweiterung finden, die Sie nutzen wollen, klicken Sie einfach auf den "Installieren"-Button daneben.

Machen Sie mit

Brackets ist ein Open-Source-Projekt. Web-Entwickler rund um die Welt helfen mit, einen besseren Code-Editor zu bauen. Noch mehr erstellen Erweiterungen, die die Möglichkeiten von Brackets erweitern. Lassen Sie uns wissen, was Sie denken, teilen Sie Ihre Ideen oder tragen Sie direkt zu dem Projekt bei.

================================================ FILE: samples/de/Erste Schritte/main.css ================================================ html { background: #e6e9e9; background-image: linear-gradient(270deg, rgb(230, 233, 233) 0%, rgb(216, 221, 221) 100%); -webkit-font-smoothing: antialiased; } body { background: #fff; box-shadow: 0 0 2px rgba(0, 0, 0, 0.06); color: #545454; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 16px; line-height: 1.5; margin: 0 auto; max-width: 800px; padding: 2em 2em 4em; } h1, h2, h3, h4, h5, h6 { color: #222; font-weight: 600; line-height: 1.3; } h2 { margin-top: 1.3em; } a { color: #0083e8; } b, strong { font-weight: 600; } samp { display: none; } img { animation: colorize 2s cubic-bezier(0, 0, .78, .36) 1; background: transparent; border: 10px solid rgba(0, 0, 0, 0.12); border-radius: 4px; display: block; margin: 1.3em auto; max-width: 95%; } @keyframes colorize { 0% { -webkit-filter: grayscale(100%); filter: grayscale(100%); } 100% { -webkit-filter: grayscale(0%); filter: grayscale(0%); } } ================================================ FILE: samples/el/Getting Started/index.html ================================================ ΞΕΚΙΝΩΝΤΑΣ ΜΕ ΤΟ BRACKETS

ΞΕΚΙΝΩΝΤΑΣ ΜΕ ΤΟ BRACKETS

Αυτός είναι ο οδηγός σας!

Καλώς ήρθατε σε μια πρώιμη προεπισκόπηση του Brackets, ένας νέος επεξεργαστής κειμένου ανοιχτού-κώδικα για τη νέα γενιά του διαδικτύου. Είμαστε μεγάλοι οπαδοί των προτύπων και θέλουμε να φτιάξουμε καλύτερα εργαλεία για JavaScript, HTML και CSS και σχετικές ανοιχτές τεχνολογίες του διαδικτύου. Αυτό είναι το ταπεινό μας ξεκίνημα.

Το Brackets είναι ένας διαφορετικής μορφής επεξεργαστής κειμένου. Μία αξιοσημείωτη διαφορά είναι ότι αυτός ο επεξεργαστής κειμένου είναι γραμμένος σε JavaScript, HTML και CSS. Αυτό σημαίνει ότι οι περισσότεροι από εσάς που χρησιμοποιούν το Brackets έχουν τις ικανότητες να τροποποιήσουν και να επεκτείνουν τον επεξεργαστή κειμένου. Στην πραγματικότητα, χρησιμοποιούμε το Brackets κάθε μέρα για να φτιάξουμε το Brackets. Επίσης, έχει και κάποιες μοναδικές λειτουργίες όπως η Γρήγορα Επεξεργασία, το Live Preview και άλλες που μπορεί να μην βρείτε σε άλλους επεξεργαστές. Για να μάθετε περισσότερα για το πως να χρησιμοποιήσετε αυτές τις λειτουργίες, συνεχίστε το διάβασμα.

Δοκιμάζουμε μερικά νέα πράγματα

Γρήγορη Επεξεργασία για CSS και JavaScript

Όχι πια εναλλαγή μεταξύ των αρχείων και απόσπαση της προσοχής. Όταν επεξεργάζεστε HTML, χρησιμοποιήστε την συντόμευση Cmd/Ctrl + E για να ανοίξετε έναν γρήγορο ενσωματωμένο επεξεργαστή που εμφανίζει το σχετικό CSS κώδικα. Κάντε την αλλαγή στον κώδικα CSS, πατήστε ESC και είστε πίσω στην επεξεργασία HTML, ή απλά αφήστε τους CSS κανόνες ανοιχτούς και αυτοί θα γίνουν μέρος του επεξεργαστή HTML. Αν πατήσεις ESC έξω από έναν γρήγορο ενσωματωμένο επεξεργαστή, θα κρυφτούν όλα.

Θέλετε να το δείτε στην πράξη; Βάλε τον κέρσορα στο tag από πάνω και πάτησε Cmd/Ctrl + E. Πρέπει να δεις ένα γρήγορο ενσωματωμένο επεξεργαστή να εμφανίζετε από πάνω, που δείχνει τον κανόνα CSS που σχετίζετε με αυτό το tag. Η Γρήγορη Επεξεργασία λειτουργεί επίσης και για class και id ιδιότητες. Μπορείτε να δημιουργήσετε και νέους κανόνες με τον ίδιο τρόπο. Κάντε κλικ σε ένα από τα tags από πάνω και πατήστε Cmd/Ctrl + E. Δεν υπάρχουν κανόνες για αυτό προς το παρόν, αλλά μπορείτε να πατήσετε το κουμπί Νέου Κανόνα για να εισάγετε έναν νέο κανόνα για το . A screenshot showing CSS Quick Edit

Μπορείς να χρησιμοποιήσεις την ίδια συντόμευση για κώδικα JavaScript για να δεις το σώμα μιας συνάρτησης με το να τοποθετείς τον κέρσορα στο όνομα της συνάρτησης που καλείτε. Για τώρα οι γρήγοροι ενσωματωμένοι επεξεργαστές δεν μπορούν να γίνουν ένθετοι, άρα μπορείς να χρησιμοποιήσεις την Γρήγορη Επεξεργασία μόνο όταν ο κέρσορας είναι μέσα σε έναν «πλήρους μεγέθους» επεξεργαστή.

Προεπισκόπηση αλλαγών HTML και CSS ζωντανά στον browser

Ξέρεις αυτό το «χορό save/reload» που κάνουμε τόσα χρόνια; Αυτό που κάνεις αλλαγές στον επεξεργαστή σου, πατάς αποθήκευση, γυρνάς στον browser και μετά κάνεις ανανέωση για να δεις επιτέλους το αποτέλεσμα; Με το Brackets, δεν χρειάζεται να κάνεις αυτόν τον χορό.

Το Brackets θα ανοίξει μία ζωντανή σύνδεση στον τοπικό σου browser και θα στείλει τους ανανεωμένους κώδικες HTML και CSS καθώς εσύ πληκτρολογείς! Μπορεί ήδη να κάνεις κάτι τέτοιο με εργαλεία που βασίζονται στον browser, αλλά με το Brackets δεν υπάρχει ανάγκη να αντιγράφεις και να επικολλάς τον τελικό κώδικα πίσω στον επεξεργαστή. Ο κώδικας σου τρέχει στον browser, αλλά ζει στον επεξεργαστή σου!

Ζωντανή επισήμανση στοιχείων HTML και κανόνων CSS

Το Brackets κάνει εύκολο το να βλέπεις πως οι αλλαγές στους κώδικες HTML και CSS θα επηρεάσουν την σελίδα. Όταν ο κέρσορας είναι σε έναν κανόνα CSS, το Brackets θα επισημάνει όλα τα στοιχεία στον browser που επηρεάζονται. Παρόμοια, όταν επεξεργάζεστε ένα αρχείο HTML, το Brackets θα επισημάνει τα αντιστοιχούμενα στοιχεία HTML στον browser.

Αν έχεις εγκατεστημένο τον Google Chrome, μπορείς να το δοκιμάσεις μόνο σου. Κάνε κλικ στο εικονίδιο της αστραπής στην πάνω δεξιά γωνία του παραθύρου του Brackets ή πατήστε Cmd/Ctrl + Alt + P. Όταν το Live Preview ενεργοποιηθεί σε ένα έγγραφο HTML, όλα τα συνδεόμενα αρχεία CSS μπορούν να επεξεργασθούν σε πραγματικό χρόνο. Το εικονίδιο θα αλλάξει από γκρι σε χρυσό όταν το Brackets δημιουργήσει την σύνδεση με τον browser. Τώρα, τοποθέτησε τον κέρσορα του στο από πάνω tag. Παρατηρήστε την μπλε επισήμανση που εμφανίζετε γύρω από την εικόνα στον Chrome. Έπειτα, χρησιμοποιήστε το Cmd/Ctrl + E για να ανοίξετε τους ορισμένους κανόνες CSS. Δοκιμάστε να αλλάξετε το μέγεθος του πλαισίου από 1px σε 10px ή αλλάξτε το χρώμα του φόντου από "dimgray" σε "hotpink". Αν έχετε το Brackets και τον browser σας να είναι δίπλα δίπλα, θα δείτε τις αλλαγές να συμβαίνουν στιγμιαία στον. Αμάτο, έτσι;

Σήμερα, το Brackets υποστηρίζει το Live Preview μόνο για HTML και CSS. Όμως, στην τρέχουσα έκδοση, οι αλλαγές σε αρχεία JavaScript ανανεώνονται αυτόματα όταν κάνετε αποθήκευση. Προς το παρόν δουλεύουμε τη υποστήριξη του Live Preview για JavaScript. Επίσης, οι άμεσες προεπισκοπήσεις του Live Preview είναι δυνατές μόνο με τον Google Chrome, αλλά ελπίζουμε να φέρουμε αυτήν την λειτουργία σε όλους τους μεγάλους browser στο μέλλον.

Γρήγορη Προβολή

Για όσους από εμάς δεν απομνημονεύσει ακόμα τις αντιστοιχίες χρωμάτων για τις τιμές HEX ή RGB, το Brackets κάνει γρήγορο και εύκολο το να βλέπεις ακριβώς ποιο χρώμα χρησιμοποιείται. Είτε σε CSS είτε σε HTML, απλά περάστε το ποντίκι πάνω από τα τιμές του χρώματος ή τις διαβαθμίσεις χρώματος και το Brackets θα εμφανίσει μια προβολή αυτού του χρώματος ή της διαβάθμισης αυτόματα. Το ίδιο συμβαίνει και με τις εικόνες: απλά περάστε πάνω από τον σύνδεσμο της εικόνας στο Brackets και αυτό θα προβάλει μια μικρογραφία αυτής της εικόνας.

Για να δοκιμάσετε την Γρήγορη Προβολή από μόνος σας, βάλτε τον κέρσορα πάνω από το tag στην κορυφή αυτού του αρχείου και πατήστε Cmd/Ctrl + E για να ανοίξετε έναν γρήγορο επεξεργαστή CSS. Τώρα απλά πηγαίνετε το βελάκι πάνω από οποιοδήποτε τιμή χρώματος μέσα στο αρχείο CSS. Επίσης, μπορείτε να το δείτε στη πράξη σε διαβαθμίσεις χρώματος με το ανοίξετε έναν γρήγορο επεξεργαστή CSS στο tag και πηγαίνοντας το βελάκι πάνω από οποιαδήποτε τιμή χρώματος του φόντου. Για να χρησιμοποιήσετε την προβολή εικόνας, βάλτε τον κέρσορα πάνω από το screenshot που συμπεριλήφθηκε προηγουμένως σε αυτό το έγγραφο.

Χρειάζεστε κάτι άλλο; Δοκιμάστε μία επέκταση!

Εκτός από όλα τα καλά που έχει το Brackets, η μεγάλη και αυξανόμενη κοινότητα των developers επεκτάσεων έχει φτιάξει πάνω από εκατό επεκτάσεις που προσθέτουν χρήσιμες λειτουργίες. Αν υπάρχει κάτι που χρειάζεστε και το Brackets δεν το προσφέρει, είναι πολύ πιθανό κάποιος να έχει φτιάξει μία επέκταση για αυτό. Για να περιηγηθήτε ή να ψάξετε τη λίστα των διαθέσιμων επεκτάσεων, πηγαίντε Αρχείο > Διαχειρηστής Επεκτάσεων και κάντε κλικ στην καρτέλα «Διαθέσιμες». Όταν βρείτε μια επέκταση που θέλετε, απλά κάντε κλικ στο κουμπί της εγκτάστασης δίπλα του.

Συμμετάσχετε

Το Brackets είναι ένα project ανοιχτού κώδικα. Web developers από όλον τον κόσμο συνεισφέρουν για να φτιάξουν έναν καλύτερο επεξεργαστή κώδικα. Πολλοί περισσότεροι φτιάχνουν επεκτάσεις που επεκτείνουν τις δυνατότητες του Brackets. Πείτε μας τι πιστεύετε, μοιραστείτε τις ιδέες σας ή συνεισφέρετε άμεσα στο project.

================================================ FILE: samples/el/Getting Started/main.css ================================================ html { background: #e6e9e9; background-image: linear-gradient(270deg, rgb(230, 233, 233) 0%, rgb(216, 221, 221) 100%); -webkit-font-smoothing: antialiased; } body { background: #fff; box-shadow: 0 0 2px rgba(0, 0, 0, 0.06); color: #545454; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 16px; line-height: 1.5; margin: 0 auto; max-width: 800px; padding: 2em 2em 4em; } h1, h2, h3, h4, h5, h6 { color: #222; font-weight: 600; line-height: 1.3; } h2 { margin-top: 1.3em; } a { color: #0083e8; } b, strong { font-weight: 600; } samp { display: none; } img { animation: colorize 2s cubic-bezier(0, 0, .78, .36) 1; background: transparent; border: 10px solid rgba(0, 0, 0, 0.12); border-radius: 4px; display: block; margin: 1.3em auto; max-width: 95%; } @keyframes colorize { 0% { -webkit-filter: grayscale(100%); filter: grayscale(100%); } 100% { -webkit-filter: grayscale(0%); filter: grayscale(0%); } } ================================================ FILE: samples/es/Primeros Pasos/index.html ================================================ PRIMEROS PASOS CON BRACKETS

PRIMEROS PASOS CON BRACKETS

¡Ésta es tu guía!

Bienvenido a Brackets, un nuevo editor de código abierto que entiende el diseño web. Es un editor de código liviano y potente al mismo tiempo que incluye herramientas visuales dentro del mismo para que puedas obtener la ayuda que necesites cuando la necesites.

Brackets es un editor diferente. Brackets tiene varias características únicas como la Edición rápida y la Vista previa dinámica y muchas más que no vas a encontrar en otros editores. Además, Brackets está escrito en JavaScript, HTML y CSS. Esto significa que la mayoría de quienes usan Brackets tienen las habilidades necesarias para modificar y extender el editor. De hecho, nosotros usamos Brackets todos los días para desarrollar Brackets. Para saber más sobre cómo utilizar estas características únicas, continúa leyendo.

Proyectos en Brackets

Para poder editar tu propio código en Brackets, puedes simplemente abrir la carpeta que contiene los archivos. Brackets considera a la carpeta abierta como el "proyecto"; características como las Sugerencias de código, la Vista previa dinámica y la Edición rápida solo utilizan los archivos contenidos dentro de la carpeta actualmente abierta.

Una vez que estés listo para salir del proyecto de ejemplo y editar tu propio código, puedes usar el menú despegable en la barra de la izquierda para cambiar de carpeta. En estos momentos, el menú despegable dice "Primeros Pasos" - la cual es la carpeta que contiene el archivo que estás viendo en estos momentos. Haz clic en el menú despegable y selecciona "Abrir carpeta…" para abrir tu carpeta. También puedes usar el menú despegable para abrir las carpetas que abriste recientemente, incluyendo este proyecto de ejemplo.

Estamos intentando algunas cosas nuevas

Edición rápida de CSS y JavaScript

Se acabó aquello de estar saltando de documento en documento perdiendo de vista lo que estás haciendo. Mientras estás escribiendo HTML, usa el atajo de teclado Cmd/Ctrl + E para abrir un editor rápido en línea con todo el contenido CSS relacionado. Ajusta tu CSS y oprime ESC para volver a tu HTML, o simplemente mantenga las reglas CSS abiertas para que pasen a formar parte de tu editor de HTML. Si pulsas ESC fuera de un editor rápido, todos se cerrarán a la vez. La edición rápida también funciona con archivos LESS y SCSS, incluyendo las reglas anidadas.

¿Quieres verlo funcionando? Coloca tu cursor sobre la etiqueta y oprime Cmd/Ctrl + E. Deberías ver aparecer un editor rápido de CSS más arriba, mostrando la regla de CSS que le afecta. La edición rápida funciona también en atributos de tipo clase e id. También puedes utilizarlo en tus archivos LESS o SCSS. Puedes crear nuevas reglas de la misma manera. Haz clic en una de las etiquetas de más arriba y oprime Cmd/Ctrl + E. Todavía no hay reglas para ese elemento, pero puedes hacer clic en el botón Nueva Regla para añadir una nueva regla a las etiquetas . Una captura de pantalla con un Editor Rápido de CSS

También puedes usar el mismo atajo para editar otras cosas--como funciones en JavaScript, colores y funciones de temporización de animaciones--y estamos añadiendo más y más continuamente.

Por ahora, no se pueden anidar editores en línea, por lo que sólo puedes usar la característica de Edición Rápida cuando el cursor está en un editor "completo".

Visualiza cambios en archivos HTML y CSS en vivo en el navegador

¿Conoces ese baile de "guardar/recargar" que llevamos años haciendo? ¿Ése en el que haces cambios en tu editor, oprimes guardar, cambias al navegador y recargas para por fin poder ver el resultado? Con Brackets, ya no tienes que hacerlo.

¡Brackets abrirá una conexión en vivo con tu navegador local y le enviará los cambios en el archivo HTML y CSS conforme escribas! Puede que ya estés haciendo algo parecido con las herramientas de desarrollo del navegador, pero con Brackets ya no necesitas copiar y pegar el código final de vuelta a tu editor. ¡Tu código se ejecuta en el navegador, pero vive en tu editor!

Resaltado en vivo de elementos HTML y reglas CSS

Brackets te ayuda a ver cómo los cambios en HTML y CSS afectan a tu página. Cuando tu cursor se encuentre sobre una regla de CSS, Brackets resaltará todos los elementos afectados en el navegador. Del mismo modo, cuando estés editando un archivo HTML, Brackets también resaltará los elementos correspondientes en tu navegador.

Si tienes instalado Google Chrome, puedes probarlo tú mismo. Haz clic sobre el icono del rayo de la esquina superior derecha o presiona Cmd/Ctrl + Alt + P. Cuando la Vista previa dinámica está funcionando en un documento HTML, todos los documentos CSS relacionados se pueden editar en tiempo real. El icono pasará de gris a dorado cuando Brackets consiga establecer una conexión con tu navegador. Ahora, coloca el cursor sobre la etiqueta que se encuentra un poco más arriba. Observa cómo aparece el resaltado azul alrededor de la imagen en Chrome. Luego, utiliza Cmd/Ctrl + E para abrir las reglas de CSS existentes. Intenta cambiar el tamaño del borde de 10px a 20px o el color del fondo de "transparent" a "hotpink". Si Brackets y tu navegador están funcionando en paralelo, verás los cambios reflejados de manera instantánea en tu navegador. Genial, ¿verdad?

Actualmente, Brackets sólo soporta Vista previa dinámica para HTML y CSS. Aún así, en la versión actual, los cambios en archivos JavaScript son recargados automáticamente en el navegador cuando guardas. En estos momentos estamos trabajando en el soporte de Vista previa dinámica para JavaScript. Las actualizaciones automáticas sólo son posibles en Google Chrome, pero esperamos poder trasladar esta funcionalidad a todos los grandes navegadores.

Vista Rápida

Para aquellos que todavía no han memorizado las equivalencias de color entre Hex y RGB, Brackets permite ver exactamente qué color se está utilizando rápida y fácilmente. Tanto en CSS como en HTML, simplemente mueve el cursor sobre cualquier valor de color o gradiente y Brackets mostrará una previsualización del mismo de manera automática. Lo mismo sirve para imágenes: simplemente pasa el cursor sobre la dirección de una imagen en Brackets, y éste mostrará una vista en miniatura de la misma.

Para probar la previsualización tú mismo, coloca el cursor en la etiqueta al principio de este documento y oprime Cmd/Ctrl + E para abrir un editor CSS. Ahora, simplemente mueve el cursor sobre cualquiera de los colores dentro del CSS. También puedes verlo funcionando en gradientes abriendo un editor de CSS en la etiqueta y pasando el cursor por cualquiera de los valores para las imágenes de fondo. Para probar la vista previa de imágenes, coloca el cursor sobre la imagen incluida antes en éste documento.

¿Necesitas algo más? ¡Prueba una extensión!

Además de todas las bondades naturales de Brackets, nuestra amplia y creciente comunidad de desarrolladores de extensiones ha creado cientos de extensiones que añaden útiles funcionalidades. Si hay algo que necesitas que Brackets no soporta, es bastante probable que alguien haya construido una extensión para ello. Para navegar o buscar en la lista de extensiones disponibles, selecciona Archivo > Gestionar extensiones... y haz clic en la pestaña "Disponibles". Cuando encuentres una que quieras, simplemente presiona el botón "Instalar" a su derecha.

Involúcrate

Brackets es un proyecto de código abierto. Desarrolladores web de todo el mundo están contribuyendo a construir un mejor editor de código. Haznos saber lo que piensas, comparte tus ideas o contribuye directamente al proyecto.

================================================ FILE: samples/es/Primeros Pasos/main.css ================================================ html { background: #e6e9e9; background-image: linear-gradient(270deg, rgb(230, 233, 233) 0%, rgb(216, 221, 221) 100%); -webkit-font-smoothing: antialiased; } body { background: #fff; box-shadow: 0 0 2px rgba(0, 0, 0, 0.06); color: #545454; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 16px; line-height: 1.5; margin: 0 auto; max-width: 800px; padding: 2em 2em 4em; } h1, h2, h3, h4, h5, h6 { color: #222; font-weight: 600; line-height: 1.3; } h2 { margin-top: 1.3em; } a { color: #0083e8; } b, strong { font-weight: 600; } samp { display: none; } img { animation: colorize 2s cubic-bezier(0, 0, .78, .36) 1; background: transparent; border: 10px solid rgba(0, 0, 0, 0.12); border-radius: 4px; display: block; margin: 1.3em auto; max-width: 95%; } @keyframes colorize { 0% { -webkit-filter: grayscale(100%); filter: grayscale(100%); } 100% { -webkit-filter: grayscale(0%); filter: grayscale(0%); } } ================================================ FILE: samples/fa-ir/Getting Started/index.html ================================================ شروع به کار با براکتس

شروع به کار با براکتس

این راهنمایی برای شماست!

به براکتس، ویرایشگر کد منبع باز پیشرفته که طراحی وب را درک می کند، خوش آمدید. آن یک چیز سبک و در عین حال قدرتمند است .ویرایشگر کد که ابزارهای بصری را در درون ویرایش گر ترکیب میکند تا به اندازه کافی کمک دریافت کنید زمانی که آن را می خواهید.

براکتس یک نوع متفاوت از ویرایشگر می باشد. براکتس برخی ویژگی های منحصر به فردی دارد مانند ویرایش سریع ،پیشنمایش زنده،و چیزها دیگری که ممکن است شما آن را در ویرایشگر های دیگر پیدا نکنید. و براکتس با جاوا اسکریپت ،اچ تی ام ال و سی اس اس نوشته شده است. به این معنا که اکثر کاربران براکتس مهارت های لازم برای توسعه و گسترش ویرایشگر را دارند. در حقیقت ما هر روز از براکتس استفاده می کنیم تا براکتس را درست کنیم. برای یادگیری بیشتر درباره اینکه چطور از ویژه گی های کلیدی استفاده کنید بر روی آن مطالعه داشته باشید.

پروژه ها در براکتس

به منظور ویرایش کد با استفاده از براکتس،شما می توانید فقط پوشه ای که شامل فایلها یتان می باشد را باز کنید. براکتس با پوشه جاری باز شده به عنوان یک "پروژه" رفتار می کند; ویژه گی نکات کد،پیشنمایش زنده و ویرایش سریع فقط در پوشه جاری باز شده قابل استفاده است.

هنگامی که شما قصد خارج شدن از پروژه را دارید و کد را ویرایش می کنید، شما می توانید از منوی باز شوند در نوار کناری سمت چپ جهت تغییر پوشه ها استفاده کنید. اکنون،منوی باز شونده نشان می دهد "Getting Started" - پوشه ای است که فایلهای که اکنون در حال مشاهده آن هستید را شامل می شود. کلیک کنید بر روی منوی باز شونده و انتخاب کنید "باز کردن پوشه"را. برای باز کردن پوشه خودتان.شما می توانید همچنین بعداً استفاده کنید جهت برگشتن به پوشه های که قبلا باز کرده اید شامل همین پروژه نمونه نیز می شود.

ویرایش سریع برای سی اس اس و جاوا اسکریپت

تعویض نه چندان زیاد بین اسناد و از دست دادن متن محتوا وقتی در حال ویرایش "اچ تی ام ال" هستید از کلید میانبر Cmd/Ctrl + E استفاده کنید تا یک ویرایشگر سطری سریع باز شود که تمام سی اس اس مربوطه را نشان دهد. یک کنترل برای سی اس اس تان بسازید و کلید ESC را فشار دهید تا به ویرایش اچ تی ام ال تان برگردید، یا فقط قواعد باز شده ی سی اس اس را ترک کنید در نتیجه آنها به بخشی از ویرایشگر اچ تی ام ال تان وارد خواهند شد. اگر شما کلید ESC کناری از یک ویرایشگر سطری سریع را فشار دهید آنها از بین می رود. ویرایش سریع همچنین قوانین تعریف شده در فایلهای LESS وSCSS که شامل قوانین تو در تو می باشند پیدا خواهد کرد.

Want to see it in action? Place your cursor on the tag above and press Cmd/Ctrl + E. You should see a CSS quick editor appear above, showing the CSS rule that applies to it. Quick Edit works in class and id attributes as well. You can use it with your LESS and SCSS files also. You can create new rules the same way. Click in one of the tags above and press Cmd/Ctrl + E. There are no rules for it right now, but you can click the New Rule button to add a new rule for . A screenshot showing CSS Quick Edit

شما می توانید استفاده کنید از میانبر ثابتی تا چیزهای دیگری را، همچنین ویرایش کنید مثل توابع در جاوااسکریپت، رنگها و توابع زمانی انیمیشن و بیش از هر زمانی استفاده می کنید.

در حال حاضر ویرایشگر های سطری نمی توانند تودرتو باشند بنابراین شما می توانید تنها ویرایشگر سریع را استفاده کنید زمانی که مکان نما در یک ویرایش تمام صفحه است.

پیش نمایش تغیرات اچ تی ام ال و سی اس اس به صورت زنده در مرورگر

شما میدانید که ما فرآیند ذخیره /بازنگری را سالهاست انجام می دهیم در ویرایشتان جای را که تغیری ایجاد می کنید ذخیره می کنید و به مرورگر می روید و سپس نو سازی، سرانجام نتیجه را مشاهده کنید؟ با براکتس شما نیازی به آن کار ندارید.

براکتس یک ارتباط زنده با مرورگر محلی تان ایجاد می کند و یک نوع بروز رسانی اچ تی ام ال و سی اس اس را قرار می دهد در داخل آن. شما همچنین ممکن است چیزهای را که امروزه انجام می دهید با ابزار های مرورگرهای رایج، اما با براکتس شما نیازی به کپی و جایگزاری و در نهایت بازگشت به ویرایشگر کد ندارید. ویرایش کدهای تان اجرا خواهند شد در مرورگر ،اما به صورت زنده . براکتس یک ارتباط زنده با مرورگر محلی تان ایجاد می کند و یک نوع بروز رسانی اچ تی ام ال و سی اس اس را قرار می دهد در داخل آن. شما همچنین ممکن است چیزهای را که امروزه انجام می دهید با ابزار های مرورگرهای رایج، اما با براکتس شما نیازی به کپی و جایگزاری و در نهایت بازگشت به ویرایشگر کد ندارید. ویرایش کدهای تان اجرا خواهند شد در مرورگر ،اما به صورت زنده .

هایلایت زنده عناصر اچ تی ام ال و قوانین سی اس اس

براکتس تغیرات ایجاد شده در اچ تی ام ال و سی اس اس و چگونگی تاثیر آن بر روی صفحه را به آسانی نشان می دهد. زمانی که نشانگر ماوس بر روی یک قانون سی اس اس قرار می گیرد براکتس تمام عناصر تحت تاثیر را مشخص می کند. مشابهاً زمانی که یک فایل html در حال ویرایش است براکتس عناصر مربوط به اچ تی ام ال را در مرورگر مشخص می کند. امروزه براکتس فقط از پیشنمایش زنده برای اچ تی ام ال و سی اس اس پشتیبانی می کند. اگر چه در نسخه جاری تغییرات در فایلهای جاوااسکریپت به طور خودکار، زمانی که شما ذخیره می کنید مجدّداً بار گذاری می شود. ما در حال کارکردن بر روی پیشنمایش زنده جهت پشتیبانی از جاوا اسکریپت هستیم. پیشنمایش زنده تنها با گوگل کروم ممکن هست، اما ما امیدواریم این قابلیت درآینده برای همه عمده مرورگرهای به کار گرفته شود.

If you have Google Chrome installed, you can try this out yourself. Click on the lightning bolt icon in the top right corner of your Brackets window or hit Cmd/Ctrl + Alt + P. When Live Preview is enabled on an HTML document, all linked CSS documents can be edited in real-time. The icon will change from gray to gold when Brackets establishes a connection to your browser. Now, place your cursor on the tag above. Notice the blue highlight that appears around the image in Chrome. Next, use Cmd/Ctrl + E to open up the defined CSS rules. Try changing the size of the border from 10px to 20px or change the background color from "transparent" to "hotpink". If you have Brackets and your browser running side-by-side, you will see your changes instantly reflected in your browser. Cool, right?

نمایش سریع

برای برخی از ما که هنوز رنگ معادل برچسب های HEX ,rgbرا حفظ نیستیم، براکتس آن را سریع و راحت ایجاد میکند تا ببینی که دقیقاً کدام رنگ است که استفاده می شود. در هر سی اس اس به سادگی نشانگر ماوس را روی هر مقدار رنگ یا گرادینت در براکتس ببریم، یک پیش نمایش از رنگ/ گرادینت به صورت خودکار نمایش داده خواهد شد. به صورت مشابه برای تصاویر هم بکار می رود. به سادگی زمانی که نشانگر بر روی /آدرس تصویر در ویرایشگر براکتس قرار گیرد به عنوان پیش نمایش تصویر بند انگشتی از عکس نشان خواهد داد.

To try out Quick View for yourself, place your cursor on the tag at the top of this document and press Cmd/Ctrl + E to open a CSS quick editor. Now simply hover over any of the color values within the CSS. You can also see it in action on gradients by opening a CSS quick editor on the tag and hovering over any of the background image values. To try out the image preview, place your cursor over the screenshot image included earlier in this document.

به چیز دیگری نیاز دارید? تلاش یک افزونه!

علاوه بر تمام مزایای که در براکتس ایجاد شده است جامعه بزرگ و در حال رشد ما که متشکل از توسعه دهنده افزونه هاست هزاران افزونه برای اضافه شدن قابلیت های بسیار مفید ساخته شده است. اگر نیاز به موارد دیگری دارید که در براکتس ارائه نشده است به احتمال زیاد شخصی افزونه ای را برای آن ایجاد کرده است به فهرست و یا جستجو لیست افزونه های در دسترس رجوع کنید در پرونده ، مدیریت افزونه ها را انتخاب کنید و بر روی برگه در دسترس کلیک کنید. زمانی که افزونه ای را که می خواهید پیدا کردید کافیست بر روی "نصب" کلیک کنید و ادامه دهید.

درگیر شدن

براکتس یک پروژه منبع باز می باشد. توسعه دهندگان وب در سرتاسر جهان در حال مشارکت جهت ساخت یک ویرایشگر بهتر می باشند. بسیاری در حال ساخت افزونه های هستند که توانایی براکتس را افزایش می دهد. آنچه را که به آن فکر می کنید را به ما اطلاع دهید ،ایده های خود را به اشتراک بگزارید یا به صورت مستقیم در پروژه مشارکت کنید.

================================================ FILE: samples/fa-ir/Getting Started/main.css ================================================ /* This is the CSS rules for the Farsi|فارسیlanguage, which is written right to left. by pooya parsa dadashi || datamweb. */ html { background-color: #e6e9e9; background-image: linear-gradient(270deg, rgb(230, 233, 233) 0%, rgb(216, 221, 221) 100%); -webkit-font-smoothing: antialiased; } body { background: #fff; box-shadow: 0 0 2px rgba(0, 0, 0, 0.06); color: #545454; font-size: 16px; line-height: 1.5; margin: 0 auto; max-width: 800px; padding: 2em 2em 4em; } h1, h2, h3, h4, h5, h6 { color: #000; font-weight: 600; line-height: 1.3; } h2 { margin-top: 1.3em; } a { color: #0083e8; } b, strong { font-weight: 600; } samp { display: none; } img { animation: colorize 2s cubic-bezier(0, 0, .78, .36) 1; background: transparent; border: 10px solid rgba(0, 0, 0, 0.12); border-radius: 4px; display: block; margin: 1.3em auto; max-width: 95%; } @keyframes colorize { 0% { -webkit-filter: grayscale(100%); filter: grayscale(100%); } 100% { -webkit-filter: grayscale(0%); filter: grayscale(0%); } } /* Starting custom rules */ .rtl { direction: rtl; font-family: "Tahoma", Helvetica, Arial, sans-serif; text-align: right; } ================================================ FILE: samples/fi/Aloitus/index.html ================================================ BRACKETSIN KÄYTÖN ALOITUS

BRACKETSIN KÄYTÖN ALOITUS

Tämä on oppaasi!

Tervetuloa käyttämään Bracketsia, nykyaikaista, avoimen lähdekoodin koodieditoria, joka ymmärtää web-suunnittelun. Se on kevyt mutta silti tehokas: koodieditori, joka sulauttaa visuaaliset työkalut suoraan editoriin, niin että saat oikean määrän apua silloin, kun haluat sitä.

Brackets on erityyppinen editori. Bracketsissa on joitakin ainutlaatuisia ominaisuuksia, kuten pikamuokkaus, reaaliaikainen esikatselu ja muita, joita et voi löytää muista editoreista. Lisäksi Brackets on kirjoitettu JavaSciptillä, HTML:llä ja CSS:llä. Se tarkoittaa, että useimmilla Bracketsin käyttäjistä on riittävät taidot muokata ja laajentaa editoria. Itse asiassa käytämme Bracketsia joka päivä sen itsensä kehitykseen. Oppiaksesi lisää siitä, kuinka käyttää avainominaisuuksia, jatka lukemista.

Projektit Bracketsissa

Muokataksesi omaa koodiasi Bracketsia käyttäen voit vain avata tiedostosi sisältävän kansion. Brackets pitää nykyistä avointa kansiota ”projektina”; ominaisuudet, kuten koodivihjeet, esikatselu ja pikamuokkaus, käyttävät vain parhaillaan avoinna olevan kansion tiedostoja.

Heti kun olet valmis luopumaan tästä näyteprojektista ja muokkaamaan omaa koodiasi, voit käyttää vasemman sivupalkin pudotusvalikkoa kansioiden vaihtamiseen. Juuri nyt pudotusvalikossa lukee ”Aloitus”. Tämä on kansio, joka sisältää tiedoston, jota tarkastelet juuri nyt. Avaa oma kansiosi napsauttamalla pudotusvalikkoa ja valitsemalla ”Avaa kansio…”. Voit käyttää pudotusvalikkoa myös myöhemmin vaihtaaksesi takaisin kansioihin, jotka olet avannut aiemmin, sisältäen tämän näyteprojektin.

Pikamuokkaus CSS:lle ja JavaScriptille

Ei lisää dokumenttien välillä vaihtamista tai asiayhteyden hukkaamista. Muokatessasi HTML:ää käytä näppäinyhdistelmää Cmd/Ctrl + E avataksesi upotetun pikaeditorin, joka näyttää kaiken tiedostoon liittyvän CSS:n. Muokkaa CSS:ääsi, paina ESC-näppäintä ja olet taas muokkaamassa HTML:ää, tai yksinkertaisesti jätä CSS-säännöt auki, ja niistä tulee osa HTML-editoriasi. Jos painat ESC-näppäintä pikaeditorin ulkopuolella, ne kaikki sulkeutuvat. Pikaeditori löytää myös LESS- ja SCSS-tiedostoissa määritellyt säännöt, sisältäen sisäkkäiset sellaiset.

Haluatko nähdä sen toiminnassa? Aseta kohdistin alla olevaan -tägiin ja paina Cmd/Ctrl + E. Sinun pitäisi nähdä, kun CSS-pikaeditori ilmestyy alapuolelle näyttäen CSS-säännön, joka pätee siihen. Pikamuokkaus toimii toki myös class- ja id-attribuuttien kanssa. Voit käyttää sitä myös LESS- ja SCSS-tiedostojesi kanssa. Voit luoda uusia sääntöjä samalla tavalla. Napsauta yhtä alaosan -tägeistä ja paina Cmd/Ctrl + E. Sille ei ole sääntöjä juuri nyt, mutta voit napsauttaa Uusi sääntö -painiketta lisätäksesi uuden säännön -tägeille. Kuvankaappaus, jossa näkyy CSS:n pikamuokkaus

Voit käyttää samaa näppäinyhdistelmää muokataksesi myös muita asioita - kuten JavaScriptin funktioita, värejä ja animaatioiden ajoitusfunktioita - ja me lisäämme koko ajan yhä enemmän ominaisuuksia.

Toistaiseksi sisäeditorit eivät voi olla sisäkkäin, joten voit käyttää pikamuokkausta vain silloin, kun kohdistin on täyskokoisessa editorissa.

Esikatsele HTML:n ja CSS:n muutoksia reaaliaikaisesti selaimessa

Tiedäthän sen ”tallenna ja päivitä -tanssin”, jota olemme harrastaneet vuosia? Se, jossa tehdään muutoksia editorissa, painetaan tallenna, vaihdetaan selaimeen ja sitten päivitetään, jotta tulos tulee viimein näkyville? Bracketsissa sinun ei tarvitse harrastaa tätä tanssia.

Brackets avaa reaaliaikaisen yhteyden paikalliseen selaimeesi ja vie HTML:n CSS:n päivitykset samalla kun kirjoitat! Saatat olla jo tehnyt jotakin tämän kaltaista selainpohjaisilla työkaluilla, mutta Bracketsin kanssa ei tarvitse kopioida ja liittää lopullista koodia takaisin editoriin. Koodisi suoritetaan selaimessasi, mutta se sijaitsee editorissasi!

Korosta HTML-elementtejä ja CSS-sääntöjä reaaliaikaisesti

Brackets tekee helpoksi nähdä, kuinka muutoksesi HTML:ssä ja CSS:ssä vaikuttavat sivuun. Kun kohdistin on CSS-säännöllä, Brackets korostaa kaikki siihen liittyvät elementit selaimessa. Samalla tavoin muokattaessa HTML-tiedostoa Brackets korostaa vastaavat HTML-elementit selaimessa.

Jos sinulla on Google Chrome asennettuna, voit kokeilla tätä itse. Napsauta Brackets-ikkunan oikeassa yläkulmassa sijaitsevaa salamakuvaketta tai paina Cmd/Ctrl + Alt + P. Kun esikatselu on käytössä HTML-dokumentissa, kaikkia linkitettyjä CSS-dokumentteja voi muokata reaaliajassa. Kuvake muuttuu harmaasta kultaiseksi, kun Brackets muodostaa yhteyden selaimeesi. Aseta kohdistin nyt yläpuolella olevaan -tägiin. Huomaa sininen korostus, joka ilmestyy kuvan ympärille Chromessa. Käytä seuraavaksi Cmd/Ctrl + E -näppäinyhdistelmää avataksesi määritellyt CSS-säännöt. Yritä muuttaa reunaviivan kokoa arvosta 10px arvoon 20px tai vaihtaa taustaväriä arvosta ”transparent” arvoon ”hotpink”. Jos Brackets ja selain ovat näytölläsi vierekkäin, näet muutosten heijastuvan välittömästi selaimeesi. Siistiä, eikö?

Nykyisellään Brackets tukee esikatselua vain HTML:lle ja CSS:lle. Kuitenkin jo nykyisessä versiossa muutokset JavaScript-tiedostoihin päivitetään automaattisesti tallentaessasi tiedoston. Työskentelemme parhaillaan esikatselun tuomiseksi JavaScriptille. Esikatselu on myös mahdollista vain Google Chromella, mutta toivomme pystyvämme tuomaan tämän toiminnon kaikille pääselaimille tulevaisuudessa.

Pikanäkymä

Niille meistä, jotka eivät vielä osaa ulkoa värien vastineita HEX- tai RGB-arvoille, Brackets tekee nopeaksi ja helpoksi nähdä täsmälleen, mitä väriä on käyttämässä. Kerta kaikkiaan osoita mitä tahansa väriarvoa tai liukuväriä joko CSS:ssä tai HTML:ssä, ja Brackets näyttää esikatselun tästä väristä tai liukuväristä automaattisesti. Sama tulee kuviin: osoita yksinkertaisesti kuvalinkkiä Brackets-editorissa ja se näyttää pienen esikatselukuvan tästä kuvasta.

Kokeile pikanäkymää itse asettamalla kohdistimesi tämän dokumentin yläosassa sijaitsevaan -tägiin ja painalla Cmd/Ctrl + E avataksesi CSS-pikaeditorin. Nyt yksinkertaisesti osoita mitä tahansa CSS:n väriarvoista. Voit myös nähdä sen toiminnassa liukuväreissä avaamalla CSS-pikaeditorin -tägille ja osoittamalla mitä tahansa taustakuva-arvoista. Kokeile kuvan esikatselua asettamalla osoittimesi tässä dokumentissa aiemmin esiintyneen kuvankaappauskuvan päälle.

Tarvitsetko jotakin muuta? Kokeile laajennusta!

Sen kaiken hyvän lisäksi, jota Bracketsiin on rakennettu, on suuri ja kasvava laajennuskehittäjien yhteisömme tehnyt satoja laajennuksia, jotka lisäävät hyödyllisiä toimintoja. Jos on jotakin, jota tarvitset ja jota Brackets ei tarjoa, enemmän kuin todennäköisesti joku on tehnyt laajennuksen siihen. Selaa saatavilla olevien laajennusten luetteloa tai hae siitä valitsemalla Tiedosto > Laajennusten hallinta ja napsauttamalla ”Saatavilla”-välilehteä. Kun löydät haluamasi laajennuksen, napsauta vain ”Asenna”-painiketta sen vierestä.

Lähde mukaan

Brackets on avoimen lähdekoodin projekti. Web-kehittäjät ympäri maailmaa osallistuvat paremman koodieditorin kehittämiseen. Vieläkin enemmän kehitetään laajennuksia, jotka laajentavat Bracketsin kykyjä. Kerro meille, mitä ajattelet. Jaa ideasi tai osallistu suoraan projektiin.

================================================ FILE: samples/fi/Aloitus/main.css ================================================ html { background: #e6e9e9; background-image: linear-gradient(270deg, rgb(230, 233, 233) 0%, rgb(216, 221, 221) 100%); -webkit-font-smoothing: antialiased; } body { background: #fff; box-shadow: 0 0 2px rgba(0, 0, 0, 0.06); color: #545454; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 16px; line-height: 1.5; margin: 0 auto; max-width: 800px; padding: 2em 2em 4em; } h1, h2, h3, h4, h5, h6 { color: #222; font-weight: 600; line-height: 1.3; } h2 { margin-top: 1.3em; } a { color: #0083e8; } b, strong { font-weight: 600; } samp { display: none; } img { animation: colorize 2s cubic-bezier(0, 0, .78, .36) 1; background: transparent; border: 10px solid rgba(0, 0, 0, 0.12); border-radius: 4px; display: block; margin: 1.3em auto; max-width: 95%; } @keyframes colorize { 0% { -webkit-filter: grayscale(100%); filter: grayscale(100%); } 100% { -webkit-filter: grayscale(0%); filter: grayscale(0%); } } ================================================ FILE: samples/fr/Premiers pas/index.html ================================================ PREMIERS PAS AVEC BRACKETS

PREMIERS PAS AVEC BRACKETS

Suivez le guide !

Bienvenue dans Brackets, un éditeur de code open source qui comprend et facilite la conception de sites web. Il s’agit d’un éditeur à la fois léger et puissant qui intègre des outils visuels directement dans son interface, de sorte que chaque opération peut devenir un véritable jeu d’enfant.

Brackets se distingue des éditeurs traditionnels, notamment par ses fonctionnalités uniques, comme l’Edition rapide ou l’Aperçu en direct, que vous ne trouverez pas forcément dans d’autres éditeurs. Cerise sur le gâteau, Brackets est écrit en JavaScript, en HTML et en CSS. Autrement dit, la grande majorité des utilisateurs de Brackets est capable de modifier et d’étendre l’éditeur. En fait, nous utilisons Brackets tous les jours pour son propre développement. Pour en savoir plus sur l’utilisation de ses fonctionnalités centrales, poursuivez votre lecture.

Projets Brackets

Pour éditer votre propre code à l’aide de l’application Brackets, il vous suffit d’ouvrir le dossier contenant vos fichiers. Brackets traite le dossier ouvert en tant que « projet » ; ainsi, les fonctionnalités Indicateurs de code, Aperçu en direct ou encore Edition rapide s’appliquent uniquement aux fichiers du dossier ouvert.

Si vous vous sentez prêt à franchir le cap et à éditer votre propre code, fermez cet exemple de projet et utilisez la liste déroulante de la barre latérale de gauche pour changer de dossier. Pour le moment, la liste déroulante s’intitule « Prise en main », ce qui correspond au dossier dans lequel se trouve le fichier fictif sur lequel vous vous exercez actuellement. Cliquez sur la liste déroulante et sélectionnez « Ouvrir un dossier… » pour accéder à votre propre dossier. Par la suite, vous pourrez tout à fait revenir aux dossiers précédemment ouverts, y compris cet exemple de projet, grâce à cette même liste déroulante.

Edition rapide des codes CSS et JavaScript

Vous ne risquez plus de perdre de vue le contexte en passant d’un document à un autre. Lorsque vous modifiez un fichier HTML, utilisez le raccourci Cmd/Ctrl + E pour ouvrir un éditeur rapide intégré qui affiche l’ensemble du code CSS associé. Peaufinez votre CSS, puis appuyez sur Echap pour revenir au format HTML, ou laissez simplement les règles CSS ouvertes afin qu’elles deviennent partie intégrante de l’éditeur HTML. Si vous appuyez sur Echap en dehors d’un éditeur intégré rapide, tous les éditeurs sont réduits. La fonction Edition rapide détecte également les règles définies dans les fichiers LESS et SCSS, y compris les règles imbriquées.

Une petite démonstration ? Placez le curseur de la souris sur la balise et tapez Cmd/Ctrl + E. Un éditeur rapide CSS apparaît en superposition, avec la règle CSS applicable. La fonction Edition rapide est également utilisable pour les attributs de classe et d’ID. Vous pouvez aussi vous en servir sur vos fichiers LESS et SCSS. Vous pouvez créer de nouvelles règles en procédant de la même manière. Cliquez sur l’une des balises en haut du document et appuyez sur Cmd/Ctrl + E. Il n’existe aucune règle associée pour le moment, mais vous pouvez cliquer sur le bouton Nouvelle règle afin d’ajouter une règle pour . Capture d’écran de la fonction Edition rapide CSS

Vous pouvez utiliser le même raccourci pour éditer d’autres éléments, comme les fonctions dans JavaScript, les couleurs ou les fonctions de temporisation d’animation ; nous ajoutons sans cesse des nouveautés.

Les éditeurs intégrés ne peuvent pas encore être imbriqués. Vous ne pouvez donc utiliser la fonction Edition rapide que lorsque le curseur se trouve dans un éditeur « plein écran ».

Affichage des modifications HTML et CSS en direct dans le navigateur

Depuis des années, nous pratiquons tous la fameuse technique « Enregistrer/Actualiser » : apporter des modifications dans l’éditeur, enregistrer, basculer vers le navigateur, puis actualiser la page pour voir le résultat. Avec Brackets, cette longue procédure appartient au passé.

Brackets se connecte en direct à votre navigateur local et transmet vos mises à jour au fur et à mesure que vous les appliquez au code HTML et CSS. Certes, il existe des outils accessibles directement depuis le navigateur qui permettent d’obtenir un résultat similaire mais, avec Brackets, vous n’avez pas besoin de copier et coller à nouveau le code final dans l’éditeur. Le navigateur lit votre code, mais c’est l’éditeur qui le fait vivre !

Mise en surbrillance en direct des éléments HTML et règles CSS

Avec Brackets, vous pouvez facilement visualiser les effets des modifications du code HTML et CSS sur la page. Lorsque vous placez le curseur sur une règle CSS, Brackets surligne tous les éléments concernés dans le navigateur. De même, lorsque vous éditez un fichier HTML, Brackets surligne les éléments HTML correspondants dans le navigateur.

Si vous avez installé Google Chrome, vous pouvez dès maintenant tester cette fonctionnalité. Cliquez sur l’icône représentant un éclair en haut à droite de la fenêtre Brackets ou utilisez la combinaison Cmd/Ctrl + Alt + P. Lorsque le module Aperçu en direct est activé sur un document HTML, tous les documents CSS associés peuvent être modifiés en temps réel. L’icône passe du gris au doré une fois que Brackets a établi la connexion à votre navigateur. Placez maintenant le curseur sur la balise . Vous constatez qu’une surbrillance bleue apparaît tout autour de l’image dans Chrome. Utilisez ensuite la combinaison Cmd/Ctrl + E pour ouvrir les règles CSS définies. Essayez de faire passer l’épaisseur de la bordure de 10 px à 20 px, ou de remplacer la couleur d’arrière-plan « transparent » par « hotpink ». Si Brackets et votre navigateur s’exécutent côte à côte, ce dernier affiche immédiatement les modifications. Plutôt sympathique, non ?

A l’heure actuelle, le module Aperçu en direct de Brackets ne fonctionne que pour le code HTML et CSS. Cependant, dans cette version, les modifications apportées aux fichiers JavaScript sont automatiquement rechargées lorsque vous enregistrez. Nous travaillons activement à la prise en charge du module Aperçu en direct pour le langage JavaScript. La fonctionnalité Aperçu en direct n’est disponible qu’avec Google Chrome, mais nous souhaitons à l’avenir la déployer sur l’ensemble des navigateurs.

Affichage rapide

Pour ceux d’entre nous qui n’ont pas encore mémorisé les équivalents en couleur des valeurs HEX ou RVB, Brackets permet d’afficher rapidement et facilement la couleur utilisée. Dans le code CSS ou HTML, placez simplement le curseur sur une valeur colorimétrique ou un dégradé, et Brackets affiche automatiquement un aperçu de la couleur ou du dégradé en question. Procédez de même pour les images : placez simplement le curseur sur le lien d’une image dans l’éditeur Brackets pour en afficher une miniature.

Testez la fonction Affichage rapide par vous-même : placez le curseur sur la balise en haut du document et appuyez sur Cmd/Ctrl + E pour ouvrir un éditeur rapide CSS. A présent, placez le curseur sur l’une des valeurs de couleur dans le code CSS. Vous pouvez également tester cette fonctionnalité sur un dégradé : ouvrez un éditeur rapide CSS sur la balise , puis passez le curseur sur l’une des valeurs de l’image d’arrière-plan. Pour essayer l’aperçu avec une image, placez le curseur sur la capture d’écran insérée plus haut dans le document.

Vous en voulez plus ? Jetez un œil du côté des extensions !

En plus de tous les atouts déjà intégrés à Brackets, notre communauté de développeurs, qui ne cesse de s’agrandir, a mis au point des centaines d’extensions qui offrent des fonctionnalités très pratiques. Si vous avez besoin d’une fonction qui ne se trouve pas dans Brackets, il est fort probable qu’un utilisateur ait créé l’extension qu’il vous faut. Pour parcourir la liste des extensions disponibles ou en rechercher une en particulier, cliquez sur Fichier > Gestionnaire d’extensions, puis ouvrez l’onglet « Disponible ». Lorsque vous trouvez l’extension qui vous convient, il vous suffit de cliquer sur le bouton Installer correspondant.

Participer

Brackets est un projet open source. Des développeurs web du monde entier participent à l’amélioration de l’éditeur de code. Nombreux sont ceux qui créent des extensions afin de développer les possibilités de Brackets. Donnez-nous votre avis, partagez vos idées ou participez directement au projet.

================================================ FILE: samples/fr/Premiers pas/main.css ================================================ html { background: #e6e9e9; background-image: linear-gradient(270deg, rgb(230, 233, 233) 0%, rgb(216, 221, 221) 100%); -webkit-font-smoothing: antialiased; } body { background: #fff; box-shadow: 0 0 2px rgba(0, 0, 0, 0.06); color: #545454; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 16px; line-height: 1.5; margin: 0 auto; max-width: 800px; padding: 2em 2em 4em; } h1, h2, h3, h4, h5, h6 { color: #222; font-weight: 600; line-height: 1.3; } h2 { margin-top: 1.3em; } a { color: #0083e8; } b, strong { font-weight: 600; } samp { display: none; } img { animation: colorize 2s cubic-bezier(0, 0, .78, .36) 1; background: transparent; border: 10px solid rgba(0, 0, 0, 0.12); border-radius: 4px; display: block; margin: 1.3em auto; max-width: 95%; } @keyframes colorize { 0% { -webkit-filter: grayscale(100%); filter: grayscale(100%); } 100% { -webkit-filter: grayscale(0%); filter: grayscale(0%); } } ================================================ FILE: samples/id/Memulai/index.html ================================================ MEMULAI DENGAN BRACKETS

MEMULAI DENGAN BRACKETS

Inilah panduan Anda!

Selamat datang di Brackets, sebuah editor modern dan open-source yang dirancang untuk mendesain web. Ringan namun bertenaga, editor ini memadukan perangkat visual ke dalamnya sehingga Anda memiliki perangkat yang tepat ketika Anda membutuhkannya.

Brackets berbeda dari editor lainnya. Brackets memiliki fitur-fitur yang unik seperti Edit Cepat, Tinjauan Langsung dan berbagai macam fitur yang mungkin tidak Anda temui di editor lain. Brackets dikembangkan dengan JavaScript, HTML and CSS. Ini berarti Anda, para pengguna Brackets sudah memiliki kemampuan untuk memodifikasi dan memperluas editor ini. Bahkan, kami menggunakan Brackets setiap hari dalam pengembangan Brackets. Untuk mempelajari cara penggunaan fitur-fitur utama yang ada di dalam Brackets, baca lebih lanjut.

Proyek di dalam Brackets

Untuk mengedit kode Anda sendiri dengan menggunakan Brackets, Anda bisa langsung membuka folder yang berisi file-file Anda. Brackets memperlakukan folder yang sedang dibuka sebagai "proyek"; fitur-fitur seperti Petunjuk Kode, Tinjauan Langsung and Edit Cepat hanya menggunakan file-file yang ada di dalam folder yang sedang dibuka.

Saat Anda siap untuk keluar dari proyek contoh ini dan mengedit kode Anda sendiri, Anda dapat menggunakan dropdown yang ada di sisi kiri sidebar untuk mengganti folder. Saat ini, dropdown Anda menunjukkan "Memulai"- itu adalah folder yang berisi file yang Anda lihat saat ini. Klik pada dropdown dan pilih "Buka Folder…" untuk membuka folder Anda sendiri. Anda juga bisa menggunakan dropdown nanti untuk berpindah ke folder yang telah Anda buka sebelumnya, termasuk proyek contoh ini.

Edit Cepat untuk CSS dan JavaScript

Tidak perlu lagi repot-repot berpindah dari satu dokumen ke dokumen lainnya. Ketika mengedit HTML, gunakan pintasan Cmd/Ctrl + E untuk membuka Edit Cepat yang menampilkan semua CSS yang sesuai. Ubah CSS Anda, kemudian tekan ESC dan Anda kembali lagi mengedit HTML, atau tetap buka CSS Anda dan kotak tersebut akan menjadi satu dengan editor Anda. Jika Anda menekan ESC diluar kotak Edit Cepat, semuanya akan langsung disembunyikan. Edit Cepat juga akan menemukan aturan dalam file LESS dan SCSS, termasuk aturan bersarang.

Mau lihat cara kerjanya? Tempatkan kursor Anda di tag di atas kemudian tekan Cmd/Ctrl + E. Anda akan melihat editor cepat CSS muncul, menunjukkan aturan CSS yang sesuai. Edit Cepat juga dapat digunakan untuk atribut class dan id. Anda juga bisa menggunakannya dengan file LESS dan SCSS. Anda bisa membuat aturan baru dengan cara yang sama. Klik salah satu tag di atas dan tekan Cmd/Ctrl + E. Saat ini tidak ada aturan untuk tag itu, namun Anda bisa klik tombol Aturan Baru untuk menambahkan aturan untuk . Screenshot menunjukkan Edit Cepat CSS

Anda juga bisa menggunakan pintasan yang sama untuk mengedit hal-hal lainnya - seperti fungsi di JavaScript, warna, dan fungsi animasi - dan kami terus menambahkan fitur lain setiap saat.

Saat ini, editor inline tidak dapat disarangkan, sehingga Anda hanya dapat menggunakan Edit Cepat ketika berada dalam mode "ukuran penuh".

Tinjau perubahan HTML dan CSS secara langsung di peramban

Anda tahu mengenai teknik "simpan/muat ulang" yang sering kita lakukan? Dimana kita mengubah file, kemudian kita simpan, lalu pindah ke peramban dan memuat ulang halaman untuk melihat hasilnya? Dengan Brackets, Anda tidak perlu melakukannya lagi.

Brackets akan membuka sambungan langsung ke peramban Anda dan memperbarui HTML dan CSS secara langsung, bahkan ketika Anda mengetik! Mungkin Anda sudah pernah melakukan hal ini dengan perangkat dari peramban, namun dengan Brackets, Anda tidak perlu lagi menyalin kode kembali ke editor. Kode Anda berjalan di peramban, namun tetap berada di editor Anda!

Soroti langsung elemen HTML dan aturan CSS

Brackets mempermudah Anda melihat bagaimana perubahan pada HTML dan CSS yang Anda buat akan mempengaruhi halaman. Ketika kursor Anda berada pada aturan CSS, Brackets akan menyoroti semua elemen yang dipengaruhi pada peramban. Hal yang sama ketika mengedit file HTML, Brackets akan menyoroti elemen HTML yang sesuai pada peramban.

Jika Anda menggunakan Google Chrome, Anda bisa mencoba ini sendiri. Klik pada ikon petir di sebelah kanan atas pada jendela Brackets Anda atau tekan Cmd/Ctrl + Alt + P. Ketika Tinjauan Langsung dijalankan di dokumen HTML, semua dokumen CSS yang terhubung bisa diedit secara langsung. Ikon petir akan berubah warna dari abu-abu menjadi emas ketika Brackets mendapatkan sambungan ke peramban Anda. Sekarang, tempatkan kursor Anda di tag di atas. Perhatikan sorotan warna biru yang muncul di sekeliling gambar pada Chrome. Lalu, tekan Cmd/Ctrl + E untuk membuka aturan CSS yang ada. Cobalah ubah ukuran border dari 10px ke 20px atau ubah warna background dari "transparent" ke "hotpink". Jika Brackets dan peramban berjalan berdampingan, Anda akan dapat melihat perubahannya secara langsung di peramban Anda. Keren, kan?

Saat ini, Brackets hanya mendukung Tinjauan Langsung untuk HTML dan CSS. Namun di versi ini, perubahan terhadap JavaScript akan dimuat ulang ketika Anda menyimpan. Saat ini kami berusaha menambahkan dukungan Tinjauan Langsung untuk JavaScript. Tinjauan Langsung juga hanya bisa dijalankan di peramban Google Chrome, namun kami berharap untuk memberikan fitur ini ke semua peramban di masa depan.

Tampilan Cepat

Bagi Anda yang belum hafal nilai RGB dan HEX sebuah warna, dengan Brackets, Anda dapat dengan mudah dan cepat untuk melihat warna apa saja yang sedang digunakan. Di CSS atau HTML, tempatkan kursor di atas nilai warna atau gradien dan Brackets akan menampilkan warna/gradien tersebut secara otomatis. Sama halnya dengan gambar: taruh kursor di atas link gambar pada editor Brackets dan menampilkan thumbnail dari gambar tersebut.

Untuk mencobanya sendiri, tempatkan kursor Anda pada tag di atas dokumen ini dan tekan Cmd/Ctrl + E untuk membuka editor cepat CSS. Setelah itu, tempatkan kursor di atas nilai warna apapun di dalam CSS tersebut. Anda juga bisa mencoba sendiri untuk gradien dengan membuka editor cepat CSS pada tag dan menempatkan kursor pada nilai background image manapun. Untuk mencoba tinjauan gambar, tempatkan kursor Anda di atas gambar screenshot yang ada di dokumen ini.

Ingin fitur lain? Tambahkan ekstensi!

Selain semua fitur-fitur keren yang ada di dalam Brackets, kami mempunyai komunitas pengembang ekstensi yang terus tumbuh jumlahnya dan sudah membuat ratusan ekstensi yang sangat berguna. Apabila Anda membutuhkan fitur yang tidak ada dalam Brackets, kemungkinan besar seseorang telah membuat ekstensi untuk itu. Untuk melihat daftar ekstensi yang tersedia, klik File > Pengelola Ekstensi lalu klik di tab "Tersedia". Ketika Anda menemukan ekstensi yang Anda inginkan, klik tombol "Instal" di sebelahnya.

Ikut terlibat

Brackets adalah proyek open-source. Pengembang web dari seluruh dunia berkontribusi untuk membangun editor yang lebih baik. Dan juga ada banyak orang yang menembangkan ekstensi untuk meningkatkan kemampuan Brackets. Beri kami saran, ide, atau berkontribusilah secara langsung untuk Brackets.

================================================ FILE: samples/id/Memulai/main.css ================================================ html { background: #e6e9e9; background-image: linear-gradient(270deg, rgb(230, 233, 233) 0%, rgb(216, 221, 221) 100%); -webkit-font-smoothing: antialiased; } body { background: #fff; box-shadow: 0 0 2px rgba(0, 0, 0, 0.06); color: #545454; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 16px; line-height: 1.5; margin: 0 auto; max-width: 800px; padding: 2em 2em 4em; } h1, h2, h3, h4, h5, h6 { color: #222; font-weight: 600; line-height: 1.3; } h2 { margin-top: 1.3em; } a { color: #0083e8; } b, strong { font-weight: 600; } samp { display: none; } img { animation: colorize 2s cubic-bezier(0, 0, .78, .36) 1; background: transparent; border: 10px solid rgba(0, 0, 0, 0.12); border-radius: 4px; display: block; margin: 1.3em auto; max-width: 95%; } @keyframes colorize { 0% { -webkit-filter: grayscale(100%); filter: grayscale(100%); } 100% { -webkit-filter: grayscale(0%); filter: grayscale(0%); } } ================================================ FILE: samples/it/Primi passi/index.html ================================================ PRIMI PASSI CON BRACKETS!

PRIMI PASSI CON BRACKETS!

Questa è la tua guida!

Benvenuto in questa versione preliminare di Brackets, un nuovo editor open-source per la nuova generazione del web. Noi siamo grandi fan degli standard e vogliamo realizzare degli strumenti migliori per lavorare in JavaScript, HTML, CSS e altre tecnologie aperte del web. Questo è il nostro umile inizio.

Brackets è un editor differente. Un importante differenza è che questo editor è scritto in JavaScript, HTML e CSS. Questo significa che la maggior parte di coloro che utilizzano Brackets, sono in grado di modificare e ampliare l'editor. Infatti, noi usiamo Brackets ogni giorno per costruire Brackets. Brackets è inoltre dotato di caratteristiche uniche come Modifica Rapida, Anteprima Live e altre caratteristiche che non trovereste altrove.

Stiamo provando alcune cose nuove

Modifica rapida per CSS e JavaScript

Non c'è più bisogno di spostarsi continuamente da un documento all'altro. Quando si lavora al codice HTML, è possibile digitare Cmd/Ctrl + E per aprire un'area di modifica rapida che mostra il CSS corrispondente. Una volta effettuata una modifica al codice CSS, basta premere ESC e si ritorna sull'HTML chiudendo l'area di modifica rapida, o è possibile lasciare aperta tale area continuando a lavorare sull'HTML. Premendo invece ESC al di fuori di tale area, tutte le aree di modifica rapida verranno chiuse.

Vuoi vederlo in azione? Posiziona il cursore nel tag sopra e premi Cmd/Ctrl + E. Dovresti vedere un'area di modifica rapida del CSS apparire sopra. Sulla destra vedrai la lista delle regole CSS corrispondenti a questo tag. Scorri semplicemente le regole con Alt + Up/Down per trovare quella che intendi modificare. Uno screenshot che mostra una finestra di modifica rapida CSS

Puoi usare la stessa combinazione di tasti per il codice JavaScript e vedere il corpo di una funzione posizionando il cursore nel nome della funzione chiamata. Per il momento gli editor in linea non possono essere nidificati, quindi è possibile usare la modifica rapida solo quando il cursore è posizionato nell'editor generale.

L'anteprima del CSS cambia in tempo reale nel browser

Conosci la solita procedura "salva/ricarica" che abbiamo fatto per anni? Fai una modifica tramite editor, premi "salva", passi alla finestra del browser e aggiorni la pagina per vedere il risultato? Con Brackets non avrai più bisogno di questo procedimento.

Brackets aprirà una connessione in tempo reale con il tuo browser e gli manderà tutte le modifiche del CSS non appena tu le avrai digitate! Forse oggi stai già facendo qualcosa di simile con qualche strumento browser-based, ma con Brackets non avrai bisogno di copiare e incollare il codice CSS definitivo nel tuo editor. Il tuo codice gira nel browser, ma risiede già nell'editor!

L'anteprima mette in risalto gli elementi HTML e le regole CSS.

Brackets rende semplice vedere come le proprie modifiche all'HTML e al CSS hanno effetto sulla pagina. Quando il cursore è su una regola CSS, nel browser verranno messi in risalto tutti gli elementi che vengono influenzati da quella regola. Similmente, quando stai modificando un file HTML, Brackets metterà in risalto nel browser gli elementi HTML corrispondenti.

Se hai Google Chrome installato, puoi provare da te. Clicca nell'icona con il fulmine nell'angolo in alto a destra della finestra di Brackets oppure premi Cmd/Ctrl + Alt + P. Quando Anteprima Live è abilitata in un documento HTML, tutti i documenti CSS collegati possono essere modificati in tempo reale. L'icona cambierà colore, dal grigio al dorato, quando Brackets stabilisce una connessione con il tuo browser. Adesso, posiziona il cursore sul tag sopra. Noterai un highlight blu apparire attorno all'imagine su Chrome. Successivamente, usa Cmd/Ctrl + E per aprire le regole CSS. Prova a cambiare la grandezza del bordo da 10px a 20px o cambia il colore di sfondo da "transparent" a "hotpink". Se Brackets e il tuo browser sono posizionati fianco a fianco, noterai istantaneamente i cambiamenti anche nel tuo browser. Bello, vero?

Oggi, Brackets supporta solo l'Anteprima Live per il CSS. Comunque, nella versione corrente, i cambiamenti ai file HTML e JavaScript sono automaticamente ricaricati quando si salva. Al momento stiamo lavorando al supporto di HTML e JavaScript nell'Anteprima Live. Inoltre le anteprime live sono supportate solo su Google Chrome, ma per il futuro speriamo di portare questa funzionalità anche sugli altri browser più comuni.

Visualizzazione Rapida

Per coloro che non hanno ancora memorizzato i colori associati ai valori HEX o RGB, Brackets rende facile e veloce vedere esattamente qual è il colore corrispondente. Nei documenti CSS e HTML basta semplicemente posizionare il mouse sopra il valore di ogni colore e Brackets mostrerà automaticamente un'anteprima di quel colore. La stessa cosa vale per le immagini: basta spostare il mouse sopra il link dell'immagine all'interno dell'editor Brackets ed esso mostrerà una miniatura di quell'immagine.

Prova la Visualizzazione Rapida, posiziona il cursore sopra il tag nella parte superiore di questo documento e premi Cmd/Ctrl + E per una modifica rapida del CSS. Adesso posiziona il mouse sopra un valore di qualche colore. Inoltre puoi vederla in azione sui gradienti aprendo una modifica veloce CSS sopra il tag e posizionando il mouse sopra qualche valore corrispondente all'immagine di sfondo. Prova l'anteprima dell'immagine, posiziona il cursore sopra lo screenshot presente all'inizio di questo documento.

Partecipa

Brackets è un progetto open source. Sviluppatori web da ogni parte del mondo stanno contribuendo per realizzare un editor migliore. Altri ancora stanno realizzando estensioni che espandono le funzionalità di Brackets. Dicci cosa ne pensi, condividi le tue idee o contribuisci direttamente al progetto.

================================================ FILE: samples/it/Primi passi/main.css ================================================ html { background: #e6e9e9; background-image: linear-gradient(270deg, rgb(230, 233, 233) 0%, rgb(216, 221, 221) 100%); -webkit-font-smoothing: antialiased; } body { background: #fff; box-shadow: 0 0 2px rgba(0, 0, 0, 0.06); color: #545454; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 16px; line-height: 1.5; margin: 0 auto; max-width: 800px; padding: 2em 2em 4em; } h1, h2, h3, h4, h5, h6 { color: #222; font-weight: 600; line-height: 1.3; } h2 { margin-top: 1.3em; } a { color: #0083e8; } b, strong { font-weight: 600; } samp { display: none; } img { animation: colorize 2s cubic-bezier(0, 0, .78, .36) 1; background: transparent; border: 10px solid rgba(0, 0, 0, 0.12); border-radius: 4px; display: block; margin: 1.3em auto; max-width: 95%; } @keyframes colorize { 0% { -webkit-filter: grayscale(100%); filter: grayscale(100%); } 100% { -webkit-filter: grayscale(0%); filter: grayscale(0%); } } ================================================ FILE: samples/ja/Getting Started/index.html ================================================ BRACKETS をはじめる前に

BRACKETS をはじめる前に

まずはこのガイドからスタート

Web デザインを認識する最新のオープンソースエディター、Brackets をご利用いただき、ありがとうございます。軽量でありながらパワフルなコードエディターでのビジュアルツールとエディターとの融合により、必要なときに必要なだけのサポートを利用することができます。

Brackets は、新しいタイプのエディターです。 Brackets にはクイック編集やライブプレビューなど、他のエディターにはないユニークな機能が備わっています。Brackets は JavaScript、HTML および CSS で記述されています。つまり、ほとんどの Brackets ユーザーは、習得済みのスキルを使って、このエディターそのものを変更したり拡張したりできるということになります。実際、アドビ社内の開発チームも、毎日 Brackets を使用して Brackets の構築を進めています。主要機能の使用方法について、下記で詳しくご説明します。

Brackets のプロジェクト

Brackets を使用して独自のコードを記述するには、自分のファイルを格納したフォルダーを開きます。 現時点で開いているフォルダーが Brackets によって「プロジェクト」として処理され、そのフォルダー内のファイルのみがコードヒント、ライブプレビュー、クイック編集などの機能で使用されます。

このサンプルプロジェクトを終了し、コードを編集する準備ができたら、左側のサイドバーのドロップダウンを使用してフォルダーを切り替えます。この時点で、ドロップダウンには「はじめに」が表示され、現在表示されているファイルが格納されたフォルダーが選択されています。ドロップダウンで「フォルダーを開く...」を選択して、使用するフォルダーを開きます。 ドロップダウンを使用して、このサンプルプロジェクトが含まれるフォルダーなど、以前に開いたフォルダーに切り替えることもできます。

CSS と JavaScript のクイック編集

いくつものドキュメントを行ったり来たりして、コードの文脈を見失うようなことはもうありません。HTML ファイルの編集中にショートカットの Command+E キーまたは Ctrl+E キーを使用すると、クイック編集用のインラインエディターが開き、関連する CSS がすべて表示されます。 ここで CSS を調整して Esc キーを押せば、HTML の編集に戻ることができます。また、CSS ルールを開いたままにしておけば、HTML エディターの一部として使用できます。カーソルがクイック編集用インラインエディターの外にあるときに Esc キーを押すと、CSS ルールはすべて折りたたまれます。また、クイック編集には、ネストされたルールなど、LESS および SCSS ファイルで定義されたルールも備わっています。

この機能を実際に使用するには、上部の タグにカーソルを合わせた状態で Command+E キーまたは Ctrl+E キーを押します。すると、上部に CSS クイック編集エディターが開き、適用する CSS ルールが表示されます。クイック編集は、クラスおよび id 属性にも使用できます。また、クイック編集は LESS および SCSS ファイルでも使用できます。 新規ルールも同じように作成できます。上部の タグのいずれかをクリックし、Command+E キーまたは Ctrl+E キーを押します。そのとき既存のルールがなければ、「新規ルール」ボタンをクリックして に新規ルールを追加できます。 CSS クイック編集のスクリーンショット

同じショートカットを使用して、JavaScript の関数、カラー、アニメーションタイミング機能など、他のエレメントも編集できます。アドビでは、この機能を継続して強化していきます。

現時点ではインラインエディターをネストすることはできませんので、クイック編集を使用できるのは、カーソルが「フルサイズ」のエディター内にある場合に限られています。

HTML および CSS の編集結果をブラウザーでライブプレビュー

私たちはずっと、保存とリロードの繰り返し作業に煩わされてきました。エディターで編集して保存し、ブラウザーに切り替えて更新してから、ようやく結果を確認できるという、あの作業です。 Brackets では、もうあの作業を行う必要はありません。

Brackets では、HTML や CSS のコード編集を行いながら、ローカルブラウザーにライブ接続し、編集結果をリアルタイムでプレビューできます。ブラウザーベースのツールの中には、現時点でもこれと似たことが可能なものもありますが、Brackets の場合は最終的にコードをエディターにコピー&ペーストする必要がありません。コードが実行されるのはブラウザー上ですが、コードがある場所は最初からずっとエディター内なのです。

HTML エレメントと CSS ルールのライブハイライト

Brackets では、HTML や CSS の編集内容がどのようにページに反映されるかを、簡単に確認することができます。Brackets で CSS ルール上にカーソルを置くと、ブラウザー上でそのルールに対応するエレメントがすべてハイライト表示されます。これと同じように、HTML ファイルを編集しているときも、ブラウザー上でそれに対応する HTML エレメントがハイライト表示されます。

お使いのコンピューターに Google Chrome がインストールされていれば、これを実際にお試しいただくことができます。Brackets ウィンドウの右上にある稲妻アイコンをクリックするか、Command+Alt+P キーまたは Ctrl+Alt+P キーを押します。HTML ドキュメントでライブプレビューが有効になっていれば、リンクされている CSS ドキュメントはすべてリアルタイムで編集可能です。 Brackets でローカルブラウザーとの接続が確立されると、アイコンがグレーから金色に変わります。 この状態で、上部の タグにカーソルを合わせます。すると、Chrome 上で画像の周囲が青くハイライト表示されます。次に、Command+E キーまたは Ctrl+E キーを押して、定義済みの CSS ルールを開きます。 枠のサイズを 10 ピクセルから 20 ピクセルに変更するか、背景色を「透明」から「ホットピンク」に変更してみてください。Brackets とブラウザーが同時に動作していれば、この変更内容が瞬時にブラウザーの表示に反映されるのを確認できます。

現時点で Brackets のライブプレビューが対応しているのは、HTML と CSS のみです。JavaScript ファイルの場合、現行バージョンでは、編集内容を保存した時点で自動的にリロードされます。現在アドビでは、JavaScript のライブプレビュー対応を進めています。また、ライブプレビューの可能なブラウザーは Google Chrome のみですが、将来的には主要なブラウザーすべてでこの機能を利用できるようにしたいと考えています。

クイックビュー

まだ 16 進数値または RGB 値に対応するカラーコードを覚えていなくても、Brackets なら使用されているカラーを正確に、しかもすばやく簡単に確認できます。CSS でも HTML でも、カラー値またはグラデーション上にカーソルを重ねるだけで、そのカラーやグラデーションが自動的に表示されます。画像の場合も同様で、Brackets エディターで画像リンク上にカーソルを重ねると、その画像のプレビューがサムネイルで表示されます。

クイックビューを実際に使ってみるには、このドキュメントの上部にある タグにカーソルを合わせます。その状態で Command+E キーまたは Ctrl+E キーを押すと、CSS クイック編集エディターが開きます。あとは、CSS 内でカラー値のどれかにカーソルを重ねるだけです。また、グラデーションに対してこの機能を使ってみるには、 タグで CSS クイック編集エディターを開き、背景画像の値にカーソルを重ねます。画像のプレビューを試す場合は、このドキュメントの前半に掲載されているスクリーンショット画像にカーソルを重ねてみてください。

拡張機能でさらに補強

Brackets に組み込まれた優れた機能に加え、拡大を続ける大規模なコミュニティで、デベロッパーらが何百もの便利な拡張機能を構築しています。Brackets にない機能を必要としたとき、ほぼ確実に構築済みの拡張機能が見つかります。使用可能な拡張機能の一覧を参照または検索するには、ファイル/拡張機能マネージャー を選択し、「入手可能」タブをクリックします。使用する拡張機能が見つかったら、その横の「インストール」ボタンをクリックします。

Brackets プロジェクトに参加

Brackets はオープンソースのプロジェクトです。世界中の Web 開発者が、優れたコードエディターの構築に貢献しています。さらに多くの人々が、Brackets の拡張機能の構築に携わっています。 ぜひ、このプロジェクトについてご意見・アイデアをお寄せください。プロジェクトへの直接的なご参加もお待ちしております。

================================================ FILE: samples/ja/Getting Started/main.css ================================================ html { background: #e6e9e9; background-image: linear-gradient(270deg, rgb(230, 233, 233) 0%, rgb(216, 221, 221) 100%); -webkit-font-smoothing: antialiased; } body { background: #fff; box-shadow: 0 0 2px rgba(0, 0, 0, 0.06); color: #545454; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 16px; line-height: 1.5; margin: 0 auto; max-width: 800px; padding: 2em 2em 4em; } h1, h2, h3, h4, h5, h6 { color: #222; font-weight: 600; line-height: 1.3; } h2 { margin-top: 1.3em; } a { color: #0083e8; } b, strong { font-weight: 600; } samp { display: none; } img { animation: colorize 2s cubic-bezier(0, 0, .78, .36) 1; background: transparent; border: 10px solid rgba(0, 0, 0, 0.12); border-radius: 4px; display: block; margin: 1.3em auto; max-width: 95%; } @keyframes colorize { 0% { -webkit-filter: grayscale(100%); filter: grayscale(100%); } 100% { -webkit-filter: grayscale(0%); filter: grayscale(0%); } } ================================================ FILE: samples/ko/Getting Started/index.html ================================================ BRACKETS 으로 시작하기

BRACKETS 으로 시작하기

이것은 당신의 가이드입니다.

웹 디자인을 이해하는 현대적인 오픈 소스 코드 편집기 인 Brackets에 오신 것을 환영합니다. 가볍고 강력한 코드 편집기로 시각 도구를 편집기에 통합하여 원할 때 도움을받을 수 있습니다.

Brackets은 다른 유형의 편집기입니다. Brackets 에는 다른 편집자에서 볼 수없는 빠른 편집, 실시간 미리보기 및 기타 기능과 같은 고유 한 기능이 있습니다. Brackets에는 JavaScript, HTML 및 CSS로 작성됩니다. 즉, 대다수 사용자는 대괄호를 사용하여 편집자를 수정하고 확장하는 데 필요한 기술을 보유하고 있습니다. 사실, 우리는 Brackets을 만들기 위해 Brackets을 매일 사용합니다. 주요 기능 사용 방법에 대한 자세한 내용은 다음을 참조하십시오.

Brackets 안에 있는 프로젝트

Brackets을 사용하여 자신의 코드를 편집하려면 파일이 들어있는 폴더를 열면 됩니다. Brackets은 현재 열려있는 폴더를 "프로젝트"로 취급합니다. 코드 힌트, 실시간 미리보기 및 빠른 편집과 같은 기능은 현재 열려있는 폴더 내의 파일에만 사용합니다.

이 샘플 프로젝트에서 벗어나 자신의 코드를 편집 할 준비가되면 왼쪽 사이드 바의 드롭 다운을 사용하여 폴더를 전환 할 수 있습니다. 지금은 드롭 다운에 "시작하기"라고 표시되어 있습니다. 바로 지금보고있는 파일이 들어있는 폴더입니다. 드롭 다운을 클릭하고 "폴더 열기 ..."를 선택하여 자신의 폴더를 엽니 다. 나중에이 드롭 다운을 사용하여이 샘플 프로젝트를 포함하여 이전에 열어 본 폴더로 다시 전환 할 수도 있습니다.

CSS 및 JavaScript 용 빠른 편집

문서를 더 이상 전환하지 않고 컨텍스트를 잃어 버리지 않습니다. HTML을 편집 할 때 Cmd / Ctrl + E 단축키를 사용하여 모든 관련 CSS를 표시하는 빠른 인라인 편집기를 엽니다. CSS를 조정하고 ESC 를 누르면 HTML 편집으로 돌아가거나 CSS 규칙을 열어두면 HTML 편집기의 일부가 됩니다. 빠른 인라인 편집기 밖에서 ESC 를 누르면 모두 축소됩니다. 빠른 편집은 중첩 된 규칙을 포함하여 LESS 및 SCSS 파일에 정의 된 규칙을 찾습니다.

그것을 실제로보고 싶습니까? 위의 태그에 커서를 놓고 Cmd / Ctrl + E 를 누르십시오. 위의 CSS 빠른 편집기가 표시되어 CSS 규칙이 적용됩니다. 빠른 편집은 클래스 및 id 속성에서도 작동합니다. LESS 및 SCSS 파일과 함께 사용할 수도 있습니다. 같은 방식으로 새 규칙을 만들 수 있습니다. 위의 태그 중 하나를 클릭하고 Cmd / Ctrl + E 를 누르십시오. 지금은 규칙이 없지만 새 규칙 버튼을 클릭하여 새 규칙을 추가 할 수 있습니다. CSS 빠른 편집을 보여주는 스크린 샷

동일한 바로 가기를 사용하여 JavaScript, 색상 및 애니메이션 타이밍 기능의 다른 기능을 편집 할 수 있으며 점점 더 많은 시간을 추가하고 있습니다

지금은 인라인 편집기를 중첩 할 수 없으므로 커서가 "전체 크기"편집기 인 경우에만 빠른 편집을 사용할 수 있습니다.

HTML 및 CSS 변경 사항을 브라우저에서 미리보기

우리가 수년간 해왔 던 "저장 / 재 장전 댄스"를 아십니까? 편집기에서 변경 한 내용을 저장하고 브라우저로 전환 한 다음 새로 고침하여 최종 결과를 확인하십시오. 괄호로, 당신은 그 춤을 할 필요가 없습니다.

Brackets은 는 로컬 브라우저에 live connection 을 열고 입력 할 때 HTML 및 CSS 업데이트를 푸시합니다. 브라우저 기반 도구를 사용하여 이미 이와 같은 작업을 수행 하고 있을 수도 있지만 Brackets을 사용하면 최종 코드를 복사하여 편집기에 다시 붙여 넣을 필요가 없습니다. 코드는 브라우저에서 실행되지만 편집기에서 실행됩니다.

라이브 HTML 요소 및 CSS 규칙 강조 표시

Brackets을 사용하면 HTML 및 CSS 변경이 페이지에 어떤 영향을 주는지 쉽게 알 수 있습니다. 커서가 CSS 규칙에 있으면 Brackets은 브라우저에서 영향을 받는 모든 요소를 강조 표시합니다.마찬가지로 HTML 파일을 편집 할 때 Brackets은 브라우저에서 해당 HTML 요소를 강조 표시합니다.

Chrome을 설치 한 경우 직접 시도해 볼 수 있습니다. 대괄호 창의 오른쪽 위 모서리에있는 번개 모양 아이콘을 클릭하거나 Cmd / Ctrl + Alt + P 를 누르십시오. HTML 문서에서 실시간 미리보기가 활성화되면 연결된 모든 CSS 문서를 실시간으로 편집 할 수 있습니다. 괄호가 브라우저에 연결되면 아이콘이 회색에서 금색으로 바뀝니다.이제 위의 태그에 커서를 놓으십시오. Chrome에서 이미지 주변에 나타나는 파란색 강조 표시를 확인합니다. 다음으로 Cmd / Ctrl + E 를 사용하여 정의 된 CSS 규칙을 엽니 다. 국경의 크기를 10px에서 20px로 변경하거나 배경색을 "투명"에서 "핫 핑크"로 변경하십시오. 대괄호가 있고 브라우저가 나란히 실행되면 변경 사항이 브라우저에 즉시 반영됩니다. 멋지다, 맞지?

현재 브라켓은 HTML 및 CSS에 대한 실시간 미리보기 만 지원합니다. 그러나 현재 버전에서는 저장시 JavaScript 파일의 변경 사항이 자동으로 다시 로드됩니다. 현재 JavaScript에 대한 라이브 미리보기 지원을 위해 노력하고 있습니다. 실시간 미리보기는 Google 크롬에서만 가능하지만 이후 모든 주요 브라우저에 기능을 제공하기를 바랍니다.

빨리보기

HEX 또는 RGB 값에 해당하는 색상을 기억하지 않은 사용자의 경우 Brackets을 사용하면 어떤 색상이 사용되는지 정확하게 볼 수 있습니다. CSS 또는 HTML에서 색상 값이나 변수 위로 마우스를 가져 가면 Brackets에 해당 색상 / 변수의 미리보기가 자동으로 표시됩니다. 이미지도 마찬가지입니다. Brackets 편집기의 이미지 링크 위로 마우스를 가져 가면 해당 이미지의 미리보기 미리보기가 표시됩니다.

빠른보기를 직접 사용해 보려면 이 상단의 태그에 커서를 올려 놓으십시오. Cmd / Ctrl + E 를 눌러 CSS 빠른 편집기를 엽니다. 이제 간단하게 CSS 내의 색상 값. 또한 태그에서 CSS 빠른 편집기를 열고 배경 이미지 값 위로 마우스를 가져 가면 그래디언트의 동작을 볼 수 있습니다. 이미지 미리보기를 시험해 보려면 이 문서 앞 부분에 있는 스크린 샷 이미지 위에 커서를 놓으십시오.

다른 것이 필요합니까? 확장 프로그램을 사용해보십시오!

Brackets에 내장 된 모든 장점에 더하여, 확장 개발자의 대규모 성장 커뮤니티에는 유용한 기능을 추가하는 수백 가지 확장 기능이 있습니다. Brackets이 제공하지 않는 것을 필요로 하는 경우 누군가가 그 확장 기능을 확장했을 가능성이 큽니다. 사용 가능한 확장 프로그램 목록을 탐색하거나 검색하려면 파일> 확장 관리자 ... 를 선택하고 '사용 가능'탭을 클릭하십시오. 원하는 확장자를 찾으면 바로 옆에 있는 "설치"버튼을 클릭하십시오.

참여하기

Brackets는 오픈 소스 프로젝트입니다. 전 세계의 웹 개발자가 더 나은 코드 편집기를 작성하는데 기여하고 있습니다. 더 많은 것은 Brackets의 기능을 확장하는 확장 기능을 구축합니다. 의견을 나누거나 아이디어를 공유하거나 프로젝트에 직접 기여하십시오.

================================================ FILE: samples/ko/Getting Started/main.css ================================================ html { background: #e6e9e9; background-image: linear-gradient(270deg, rgb(230, 233, 233) 0%, rgb(216, 221, 221) 100%); -webkit-font-smoothing: antialiased; } body { background: #fff; box-shadow: 0 0 2px rgba(0, 0, 0, 0.06); color: #545454; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 16px; line-height: 1.5; margin: 0 auto; max-width: 800px; padding: 2em 2em 4em; } h1, h2, h3, h4, h5, h6 { color: #222; font-weight: 600; line-height: 1.3; } h2 { margin-top: 1.3em; } a { color: #0083e8; } b, strong { font-weight: 600; } samp { display: none; } img { animation: colorize 2s cubic-bezier(0, 0, .78, .36) 1; background: transparent; border: 10px solid rgba(0, 0, 0, 0.12); border-radius: 4px; display: block; margin: 1.3em auto; max-width: 95%; } @keyframes colorize { 0% { -webkit-filter: grayscale(100%); filter: grayscale(100%); } 100% { -webkit-filter: grayscale(0%); filter: grayscale(0%); } } ================================================ FILE: samples/nl/Aan-de-slag/index.html ================================================ AAN DE SLAG MET BRACKETS

AAN DE SLAG MET BRACKETS

Dit is uw gids!

Welkom bij een vroeg voorbeeld van Brackets, een nieuwe open-source editor voor de volgende generatie van het web. Wij zijn erg fan van de standaarden en willen een betere tool bouwen voor het bewerken van JavaScript, HTML en CSS en andere gerelateerde open web technologieën. Dit is ons bescheiden begin.

Brackets is een ander soort editor. Een opmerkelijk verschil is dat deze editor is geschreven in JavaScript, HTML en CSS. Dit betekent dat de meesten van jullie beschikken over de vaardigheden die nodig zijn om Brackets aan te passen en uit te breiden. Eigenlijk gebruiken we Brackets elke dag om Brackets te bouwen. Brackets heeft ook een paar unieke functies zoals Quick Edit, Live preview en nog meer, welke je niet kunt terugvinden in andere editors. Voor meer informatie over hoe u deze functies kunt gebruiken kunt u hier verder lezen.

We proberen een paar nieuwe dingen uit

Quick editor voor CSS en JavaScript

Nooit meer schakelen tussen documenten en verliezen van je koppeling. Bij het bewerken van HTML, kun je gebruik maken van de Cmd/Ctrl + E sneltoets om snel de gekoppelde CSS code te laten zien. Maak een verandering in je CSS, druk op ESC en je bent weer terug bij het bewerken van de HTML, of laat de CSS code open en ze maken deel uit van de HTML-editor. Als je drukt op ESC buiten de inline editor, zullen ze allemaal uitvouwen.

Wil je het in actie zien? Plaats je cursor op de tag en druk op Cmd/Ctrl + E. Je ziet dan een snelle CSS editor verschijnen hierboven, wat de CSS regels laat zien wat er aan gekoppeld is. Quick Edit werkt in zowel class en id attributen. Je kan nieuwe regels maken via dezelfde manier. Klik in een van de tags hierboven en druk op Cmd/Ctrl + E. Er zijn nu geen regels voor, maar je kunt klikken op de "Nieuwe regel"" knop om een nieuwe regel toe te voegen voor Een screenshot van de Quick CSS editor

Je kunt dezelfde sneltoets gebruiken om andere dingen te bewerken--zoals functies in JavaScript, kleuren, en animatie timing functies--en we voegen meer en functies meer toe. Nu kunnen inline editors niet genesteld worden, dus kun je alleen Quick Editor gebruiken wanneer de cursor in een "volledige grootte" editor staat.

Bekijk HTML en CSS aanpassingen live in de browser

Wist je nog die "opslaan en herlaad dans" die we jaren lang hebben gedaan? Dat je het bestand die je hebt gewijzigd in een editor moest opslaan, naar de browser moest omschakelen en dan herladen om het resultaat te kunnen zien. Met Brackets is dat allemaal verleden tijd.

Brackets opent een live connectie in je locale browser en elke keer wanneer er HTML en CSS updates zijn, werkt het automatisch bij terwijl je typt! Misschien heb je zoiets al gebruikt met browser-gebaseerde tools, maar met Brackets is het niet nodig om helemaal terug te gaan naar de editor en de code uiteindelijk te kopiëeren en te plakken. Je code wordt uitgevoerd in de browser, terwijl je blijft in de editor!

Live highlight van HTML elementen en CSS regels

Brackets maakt het makkelijk om te zien hoe wijzigingen in je HTML en CSS de pagina zullen beïnvloeden. Wanneer je je cursor op een CSS-regel zet, zal Brackets alle betrokken elementen in de browser markeren. Ook wanneer je op dat moment een HTML-bestand aan het bewerken bent, zal Brackets de bijbehorende HTML-elementen in de browser markeren.

Als je Google Chrome hebt geïnstalleerd, kunt u deze functie zelf uitproberen. Klik op het icoontje met de bliksemschicht rechtsboven in Brackets of druk op Cmd/Ctrl + Alt + P. Wanneer Live Voorbeeld is ingeschakeld op een HTML-document kunnen ook alle gekoppelde CSS-documenten worden bewerkt. Het icoontje verandert van grijs naar goud als Brackets verbinding heeft met de browser. Hierna plaats je je cursor op de tag hierboven. Let op de blauwe markering die verschijnt rond het beeld in Chrome. Vervolgens drukt u op Cmd/Ctrl + E om de gedefinieerde CSS-regels te laten zien. Probeer de grootte van de rand te veranderen van 10px naar 20px of verander de achtergrondkleur van "transparent" naar "hotpink". Als je Brackets en je browser naast elkaar hebt weergegeven, zie je dat de wijzigingen meteen worden weergegeven in zowel de browser als in Brackets. Cool toch?

Vandaag de dag ondersteunt Brackets alleen Live Preview voor HTML en CSS. We zijn momenteel bezig met een Live Preview ondersteuning voor JavaScript. Live-previews zijn ook alleen mogelijk met Google Chrome, maar we hopen dat deze functionaliteit in de toekomst voor alle belangrijke browsers beschikbaar is.

Quick View

Voor iedereen die de waardes voor HEX en RGB nooit geleerd hebben, heeft Brackets iets om snel en eenvoudig een bepaalde kleur te laten zien. Voor zowel CSS of HTML werkt het als volgt: je zet gewoon je cursor op een kleurwaarde of gradiënt en Brackets geeft een voorbeeld van de kleur. Hetzelfde geldt voor afbeeldingen: je zet je cursor op een link die naar een afbeelding leidt en Brackets laat een thumbnail zien van dat beeld.

To try out Quick View for yourself, place your cursor on the tag at the top of this document and press Cmd/Ctrl + E to open a CSS quick editor. Now simply hover over any of the color values within the CSS. You can also see it in action on gradients by opening a CSS quick editor on the tag and hovering over any of the background image values. To try out the image preview, place your cursor over the screenshot image included earlier in this document. Om Quick View uit te proberen voor jezelf, plaats je je cursor op de tag bovenaan dit document en druk op Cmd/Ctrl + E om de Quick Editor voor CSS te openen. Ga nu met de muis over een kleurwaarde heen en je ziet de kleur. Je kunt met deze functie ook gradiënts zien door met de cursor op de tag te staan en met de cursor over een van de afbeeldingskoppelingen te gaan. Om dit uit te proberen, plaats je muis over de afbeelding van het screenshot in dit document.

Verder nog iets? Gebruik een extension!

Naast al deze functies die zijn ingebouwd in Brackets, heeft onze grote en groeiende gemeenschap van extensie-ontwikkelaars meer dan honderd extensies gebouwd die nuttige functionaliteiten toevoegen. Mis je iets wat Brackets niet aanbiedt? Dan heeft waarschijnlijk iemand het al gebouwd in een extensie. Om te bladeren of de lijst met extensies te zoeken, ga naar Bestand > Uitbreidingbeheer en klik op de "Beschikbaar" tab. Als je een extensie vindt die je wil, klik je gewoon op de "Installeer" knop.

Projecten in Brackets

Om je eigen code te bewerken met Brackets, kun je gewoon een map openen met je bestanden. Brackets behandelt de geopende map als een "project"; met functies zoals Code Hints, Live Voorbeelden en Quick Edit gebruiken alleen de bestanden in de geopende map.

Zodra u klaar bent om dit voorbeeldproject te bewerken en uw eigen code toe te voegen, kunt u de dropdown gebruiken in de linker zijbalk. Op dit moment is er automatisch gekozen voor de map "Aan de slag" - dat is de map met het bestand waar je nu in bezig bent. Klik op de dropdown en kies "Open map..." om uw eigen map te openen. Je kunt de dropdown later ook gebruiken om terug te schakelen naar de mappen die je eerder hebt geopend, inclusief dit voorbeeldproject.

Doe mee

Brackets is een open-source project. Web ontwikkelaars over de hele wereld dragen bij om samen een betere code-editor te bouwen. Veel meer extensies die mogelijkheden van Brackets uitbreiden. Laat ons weten wat je denkt, geef ons uw ideeën of draag direct bij aan het project.

================================================ FILE: samples/nl/Aan-de-slag/main.css ================================================ html { background: #e6e9e9; background-image: linear-gradient(270deg, rgb(230, 233, 233) 0%, rgb(216, 221, 221) 100%); -webkit-font-smoothing: antialiased; } body { background: #fff; box-shadow: 0 0 2px rgba(0, 0, 0, 0.06); color: #545454; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 16px; line-height: 1.5; margin: 0 auto; max-width: 800px; padding: 2em 2em 4em; } h1, h2, h3, h4, h5, h6 { color: #222; font-weight: 600; line-height: 1.3; } h2 { margin-top: 1.3em; } a { color: #0083e8; } b, strong { font-weight: 600; } samp { display: none; } img { animation: colorize 2s cubic-bezier(0, 0, .78, .36) 1; background: transparent; border: 10px solid rgba(0, 0, 0, 0.12); border-radius: 4px; display: block; margin: 1.3em auto; max-width: 95%; } @keyframes colorize { 0% { -webkit-filter: grayscale(100%); filter: grayscale(100%); } 100% { -webkit-filter: grayscale(0%); filter: grayscale(0%); } } ================================================ FILE: samples/pl/Szybki Start/index.html ================================================ SZYBKI START Z BRACKETS

SZYBKI START Z BRACKETS

Oto Twój przewodnik!

Witaj we wczesnej wersji Brackets, edytorze open-source dla sieci nowej generacji. Jesteśmy wielkimi fanami standardów i chcemy budować lepsze narzędzia dla takich języków jak JavaScript, HTML i CSS oraz powiązanych z nimi technologii sieciowych. Taki był nasz skromny początek.

Patrzysz na wczesną wersję Brackets. Na wiele sposobów Brackets jest inny niż typowy edytor. Jedną z różnic jest to, że edytor ten został napisany w języku JavaScript. Tak więc, mimo iż może on nie być jeszcze gotowy do codziennego użytku, my używamy go codziennie, by budować Brackets.

Testujemy kilka nowych rzeczy

Szybka edycja CSS i JavaScript

Podczas edycji HTMLa użyj skrótu Cmd/Ctrl + E aby otworzyć szybki wbudowany edytor, który wyświetli wszystkie powiązane style CSS. Wykonaj modyfikacje w kodzie CSS, wciśnij ESC i wróć do edycji HTML. Możesz również pozostawić wbudowany edytor otwarty, tak aby stał się częścią edytora HTML. Jeśli wciśniesz ESC poza obszarem szybkiego edytora, jego wszystkie okna zostaną zamknięte. Koniec z przełączaniem się pomiędzy dokumentami i byciem wyrwanym z kontekstu.

Chcesz go zobaczyć w akcji? Umieść kursor w tagu i naciśnij Cmd/Ctrl + E. Szybki edytor CSS pojawi się nad tym tagiem. Po prawej stronie zobaczysz listę wszystkich reguł, które są powiązane z tym tagiem. Przełączaj się pomiędzy regułami używając skrótu Alt + Góra/Dół aby znaleźć ten, który chcesz edytować. Zrzut ekranu pokazujący szybką edycję CSS

Możesz użyć tego samego skrótu w kodzie JavaScript, aby zobaczyć kod funkcji, którą przywołujesz, poprzez umieszczenie kursora na nazwie funkcji. W ten sam sposób możesz również otworzyć narzędzie wybierania koloru. Po prostu umieść kursor na dowolnym kolorze zapisanym w formacie hex, rgb lub hsl. Na razie wbudowany szybki edytor nie może być niezagnieżdżony, więc możesz używać go jedynie w "pełnowymiarowym" edytorze.

Zmiany w kodzie CSS widoczne na żywo w przeglądarce

Zapewne znany Ci jest taniec w stylu "zapisz/przeładuj", który wykonywaliśmy przez ostatnie lata. Dokonujesz zmian w edytorze, zapisujesz zmiany, przełączasz się do przeglądarki i odświeżasz stronę, by w końcu zobaczyć rezultat Twojej pracy. Z Brackets taki taniec nie jest konieczny.

Brackets utworzy połączenie na żywo z Twoją lokalną przeglądarką i będzie jej przekazywał wszelkie zmiany w CSS. Być może już dzisiaj korzystasz z podobnego rozwiązania przy użyciu narzędzi w przeglądarce, jednak z Brackets nie ma potrzeby wklejania kodu CSS z powrotem do edytora. Twój kod działa w przeglądarce, ale żyje w edytorze.

Jeśli posiadasz zainstalowaną przeglądarkę Google Chrome, możesz wypróbować to już teraz. Kliknij ikonkę błyskawicy (stąd nazwa: Błyskawiczny Podgląd) w prawym górnym rogu lub naciśnij skrót Cmd/Ctrl + Alt + P. Kiedy Błyskawiczny Podgląd zostanie włączony w dokumencie HTML, wszystkie połączone z nim pliki CSS mogą być edytowane w czasie rzeczywistym. Ikona zmieni kolor z szarej na złotą gdy Błyskawiczny Podgląd nawiąże połączenie z Twoją przeglądarką. Teraz umieść kursor na tagu i wciśnij Cmd/Ctrl + E aby otworzyć zdefiniowane style CSS dla tego tagu. Spróbuj zmienić rozmiar obramowania z 10px do 20px lub zmienić kolor tła z "transparent" na "hotpink". Jeśli masz umieszczone okna przeglądarki i Brackets obok siebie zobaczysz, że zmiany są natychmiast uwzględniane w przeglądarce. Nieźle, co?

Na dzień dzisiejszy Błyskawiczny Podgląd działa jedynie dla plików CSS. Aktualnie pracujemy nad tym, aby dodać obsługę plików HTML i JavaScript. W aktualnej wersji zmiany w plikach HTML lub JavaScript są automatycznie uwzględniane w momencie zapisu. Błyskawiczny Podgląd działa jedynie z Google Chrome. Pragniemy wprowadzić tą funkcję we wszystkich najważniejszych przeglądarkach i mamy nadzieję na współpracę ze strony autorów tych przeglądarek, by stało się to faktem.

Zaangażuj się

Brackets jest projektem open-source. Web developerzy z całego świata angażują się, by budować coraz to lepszy edytor kodu. Daj nam znać co o nim sądzisz, podziel się swoimi pomysłami lub dodaj coś bezpośrednio do projektu.

================================================ FILE: samples/pl/Szybki Start/main.css ================================================ html { background: #e6e9e9; background-image: linear-gradient(270deg, rgb(230, 233, 233) 0%, rgb(216, 221, 221) 100%); -webkit-font-smoothing: antialiased; } body { background: #fff; box-shadow: 0 0 2px rgba(0, 0, 0, 0.06); color: #545454; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 16px; line-height: 1.5; margin: 0 auto; max-width: 800px; padding: 2em 2em 4em; } h1, h2, h3, h4, h5, h6 { color: #222; font-weight: 600; line-height: 1.3; } h2 { margin-top: 1.3em; } a { color: #0083e8; } b, strong { font-weight: 600; } samp { display: none; } img { animation: colorize 2s cubic-bezier(0, 0, .78, .36) 1; background: transparent; border: 10px solid rgba(0, 0, 0, 0.12); border-radius: 4px; display: block; margin: 1.3em auto; max-width: 95%; } @keyframes colorize { 0% { -webkit-filter: grayscale(100%); filter: grayscale(100%); } 100% { -webkit-filter: grayscale(0%); filter: grayscale(0%); } } ================================================ FILE: samples/pt-br/Primeiros Passos/index.html ================================================ PRIMEIROS PASSOS COM BRACKETS

PRIMEIROS PASSOS COM O BRACKETS

Este é o seu guia!

Bem-vindo ao Brackets, um moderno editor de código open-source que entende de web design. Leve, mas poderoso, ele combina ferramentas visuais no editor para que você obtenha a quantidade certa de ajuda quando precisar.

O Brackets é um editor diferente. O Brackets tem algumas características únicas, como a Edição Rápida (Quick Edit), Visualização ao vivo (Live Preview) e muitas outras que você não encontrará em outros editores. E o Brackets é escrito em JavaScript, HTML e CSS. Isso significa que a maioria de vocês que utilizam o Brackets possuem as habilidades necessárias para modificar e estender o editor. Na verdade, usamos o Brackets todos os dias para desenvolver o Brackets. Para saber mais sobre como utilizar os principais recursos, continue lendo.

Projetos no Brackets

Para editar seu próprio código utilizando o Brackets, basta abrir a pasta contendo seus arquivos. O Brackets trata a pasta atualmente aberta como um "Projeto", recursos como Code Hints, Live Preview e Quick Edit usam apenas os arquivos da pasta atualmente aberta.

Assim que você estiver pronto para sair desse projeto exemplo e editar seu próprio código, você pode usar o menu na barra lateral para trocar de pasta. Nesse momento, o menu diz "Primeiros Passos" - que é a pasta que possui o arquivo que você está olhando agora mesmo. Clique no menu e escolha "Abrir pasta..." para abrir sua própria pasta. Também é possível utilizar o menu para voltar a pastas que você abriu anteriormente, incluindo esse projeto de exemplo.

Quick Edit (Edição Rápida) de CSS e JavaScript

Sem mais necessidade de ficar trocando entre seus arquivos e perder o contexto. Quando estiver editando HTML, use o atalho Cmd/Ctrl + E para abrir um rápido editor em linha (quick inline editor) que mostra todo o CSS relacionado. Faça uma modificação no seu CSS, aperte ESC e você está de volta a edição do HTML, ou apenas deixe as regras CSS abertas e elas se tornarão parte do seu editor HTML. Se você apertar ESC fora do quick inline editor, todos eles irão se recolher. Quick Edit também encontrará regras definidas em arquivos LESS e SCSS, incluindo regras aninhadas.

Quer vê-lo em ação? Coloque o cursor sobre a tag acima e pressione Cmd/Ctrl + E. Você deverá ver um editor rápido de CSS aparecer acima, mostrando as regras CSS que estão aplicadas a ele. Quick Edit funciona em classes e atributos id também. Você pode usá-lo com seus arquivos LESS e SCSS também. Você pode criar novas regras da mesma maneira. Clique em uma das tags acima e aperte Cmd/Ctrl + E. Não existem regras para ele nesse momento, mas você pode clicar no botão Nova Regra e adicionar uma nova regra para . Um screenshot mostrando o editor rápido de CSS

Você também pode usar o mesmo atalho para editar outras coisas - como funções em JavaScript, cores e funções de tempo de animação - e nós estamos adicionando mais e mais o tempo todo.

Por enquanto inline editors não podem ser aninhados, então você só pode usar Quick Edit enquanto o cursor estiver em um editor de tamanho máximo.

Visualize alterações no HTML e CSS ao vivo no navegador

Você sabe aquela "dança salvar/recarregar" que temos feito há anos? Aquela onde você faz mudanças no seu editor, clica em salvar, alterna para o navegador e então recarrega a página para finalmente ver o resultado? Com o Brackets, você não precisa fazer essa dança.

O Brackets vai abrir uma conexão ao vivo com o seu navegador local e vai enviar as atualizações no CSS enquanto você digita! Você já deve estar fazendo alguma coisa como esta hoje com ferramentas baseadas em navegador, mas com o Brackets não há necessidade de copiar e colar o CSS final de volta para o editor. Seu código é executado no navegador, mas vive em seu editor!

Destaque ao vivo de elementos HTML e regras CSS

O Brackets facilita ver como as mudanças no seu HTML e CSS irão afetar a página. Quando seu cursor está em uma regra CSS, o Brackets irá destacar todos os elementos afetados no navegador. Similarmente, quando estiver editando um arquivo HTML, o Brackets irá destacar o elemento HTML correspondente no navegador.

Se você tem o Google Chrome instalado, você pode tentar fazer isso sozinho. Clique no ícone em forma de raio no canto superior direito ou pressione Cmd/Ctrl + Alt + P. Quando a Visualização ao Vivo (Live Preview) é habilitada em um documento HTML, todos os documentos CSS vinculados podem ser editados em tempo real. O ícone vai mudar de cinza para ouro quando o Brackets estabelecer uma conexão com o seu navegador. Agora, coloque o cursor sobre a tag acima e use Cmd/Ctrl + E para abrir as regras CSS definidas. Tente mudar o tamanho da borda de 10px para 20px ou alterar a cor de fundo de "transparent" para "hotpink". Se você tem o Brackets e seu navegador rodando lado a lado, você vai ver as alterações refletidas instantaneamente no seu navegador. Legal, certo?

Atualmente, o Brackets suporta Visualização ao Vivo (Live Preview) apenas para HTML e CSS. Entretanto, na versão atual, mudanças nos arquivos JavaScript são automaticamente carregadas quando você salva. Estamos trabalhando no suporte à Visualização ao Vivo para JavaScript. Visualizações ao vivo atualmente só são possíveis com Google Chrome, mas nós esperamos trazer essa funcionalidade para os principais navegadores no futuro.

Quick View

Para aqueles de nós que ainda não memorizaram os códigos de cores equivalentes para HEX e RGB, o Brackets facilita ver exatamente qual cor está sendo utilizada. Tanto no CSS quanto no HTML, basta passar o mouse por cima de qualquer valor de cor ou gradiente que o Brackets irá mostrar uma pré-visualização da cor/gradiente. O mesmo vale para imagens: passe o mouse por cima do link da imagem no Brackets e ele irá mostrar uma pré-visualização dessa imagem.

Tente o Quick View você mesmo, passe o cursor na tag no topo desse documento e aperte Cmd/Ctrl + E para abrir o editor rápido de CSS. Agora simplesmente passe o mouse por cima de qualquer um dos valores de cor no CSS. Você também pode ver isso em ação em gradientes abrindo um editor rápido de CSS na tag e passando o mouse por qualquer um dos dos valores de background-image. Tente também a pré-visualização de imagens, coloque o mouse sobre o link de screenshot incluído anteriormente nesse documento.

Precisa de algo mais? Tente uma extensão!

Além de todas as coisas boas construídas no Brackets, nossa grande comunidade de desenvolvedores de extensões tem construído centenas de extensões que adicionam funcionalidades uteis ao editor. Se tem algo que você precisa que o Brackets não oferece, mais do que provavelmente alguém já construiu uma extensão para isso. Para pesquisar na lista de extensões disponíveis, escolha Arquivo > Gerenciador de Extensões... e clique na aba "Disponíveis". Quando encontrar uma extensão que você quer, apenas clique no botão "Instalar" ao lado.

Envolva-se

O Brackets é um projeto open-source. Desenvolvedores web de todo o mundo estão a contribuir para criar um editor de código melhor. Muitos outros estão desenvolvendo extensões que espandem as capacidades do Brackets. Diga-nos o que você pensa, compartilhe as suas ideias ou contribua diretamente para o projeto.

================================================ FILE: samples/pt-br/Primeiros Passos/main.css ================================================ html { background: #e6e9e9; background-image: linear-gradient(270deg, rgb(230, 233, 233) 0%, rgb(216, 221, 221) 100%); -webkit-font-smoothing: antialiased; } body { background: #fff; box-shadow: 0 0 2px rgba(0, 0, 0, 0.06); color: #545454; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 16px; line-height: 1.5; margin: 0 auto; max-width: 800px; padding: 2em 2em 4em; } h1, h2, h3, h4, h5, h6 { color: #222; font-weight: 600; line-height: 1.3; } h2 { margin-top: 1.3em; } a { color: #0083e8; } b, strong { font-weight: 600; } samp { display: none; } img { animation: colorize 2s cubic-bezier(0, 0, .78, .36) 1; background: transparent; border: 10px solid rgba(0, 0, 0, 0.12); border-radius: 4px; display: block; margin: 1.3em auto; max-width: 95%; } @keyframes colorize { 0% { -webkit-filter: grayscale(100%); filter: grayscale(100%); } 100% { -webkit-filter: grayscale(0%); filter: grayscale(0%); } } ================================================ FILE: samples/pt-pt/Primeiros Passos/index.html ================================================ PRIMEIROS PASSOS COM BRACKETS

PRIMEIROS PASSOS COM BRACKETS

Este é o teu guia!

Bem vindo a esta pré-vizualização do Brackets, um novo editor open-source para a proxima geração web. Somos grandes fãs de padrões e queremos construir uma melhor ferramenta para JavaScript, HTML e CSS e relacionadas com tecnologias web abertas. Este é o nosso humilde começo.

Estás a olhar para uma versão antecipada of Brackets Em muitas formas, Brackets é um tipo de editor diferente. Uma grande diferença é que este editor é escrito em JavaScript. Embora o Brackets possa não estar pronto para o use dia-a-dia por enquanto, estamos a usa-lo todos os dias para construir o Brackets.

Estamos a tentar algumas coisas novas

Edição Rápida para CSS e JavaScript

Ao editar HTML, use as teclas de atalho Cmd/Ctrl + E para abrir um rapidamente o editor de linha que mostra todo o CSS relacionado. Faça uma pequena alteração no seu CSS, pressione ESC e está de volta a editar HTML. Ou simplesmente deixe as regras de CSS abertas e elas tornam-se parte do seu editor de HTML. Se pressionar a tecla ESC fora do Editor Rápido, todas elas fecham. Sem mais trocas de ficheiros e perdar o fio à meada.

Queres ver isto em acção? Posiciona o teu cursor sobre a tag e pressiona Cmd/Ctrl + E. Deves ver o Editor Rápido de CSS aparecer. Na direita vais ver uma lista de regras CSS relacionadas com essa tag. Simplesmente navegue pela list de regras com Alt + Up/Down para encontrar aquela que queres editar. Um screenshot a mostrar a Edição Rápida de CSS

Pré-Vizualizar mudança no CSS ao vivo no navegador

Sabes que a "dança guardar/recarregar" que fizemos por anos? Aquela que fazes uma mudança no editor, pressionas guardar, mudas para o navegador e precionas recarregar para ver o resultado? Com o Brackets, não precisas de fazer essa dança.

Brackets abre uma live connection para o teu navegador e envia actualizações do CSS assim que escreves! Probavelmente já fazias uma coisa parecida com ferramentas de navegador, mas com o Brackets não é preciso copiar e colar o código CSS final de novo no editor. O teu código corre no navegador, e vive no teu editor!

Se tens o Google Chrome instalado, podes tentar isto por ti próprio. Clica no icone do relampago no canto superior direito ou pressiona Cmd/Ctrl + Alt + P. Quando o Live Preview está ligado a um documento HTML, todo o css ligado a ele pode ser editado em temo real. O icone muda de cinzento para dourado quando o Brackets estabelece a ligação ao navegador. Agora, ponha o seu curso na tag em cima (linha 56) e pressione Cmd/Ctrl + E Para abrir as regras CSS para aquela tag. Tenta mudar o tamanho da borda de 10px par 20px ou muda a cor de fundo de "transparent"(nome da cor) para "hotpink"(nome da cor). Se o Brackets e o teu navegador estão a correr lado-a-lado, vais ver que as mudanças são instantaneas no teu navegador. Altamente, certo?

Hoje, o Brackets só suporta Live Preview para CSS. Estamos de momento a trabalhar no Live Preview para HTML e JavaScript. Na versãp actual, não vai ver mudanças no seu ficheiro HTML até guardar o documento. Live previews são só possiveis com Google Chrome. Nós queremos trazer esta funcionalidade para todos os navegadores mais conhecidos, e estamos ansiosos por trabalhar com esses fornecedores.

Envolva-se

Brackets é um projeto open-source. Desenvolvedores Web de todo o mundo estão a contribuir para construir um melhor editor de código. Diz-nos o que pensas, partilha as tuas ideias or contribui diretamente para o projeto.

================================================ FILE: samples/pt-pt/Primeiros Passos/main.css ================================================ html { background: #e6e9e9; background-image: linear-gradient(270deg, rgb(230, 233, 233) 0%, rgb(216, 221, 221) 100%); -webkit-font-smoothing: antialiased; } body { background: #fff; box-shadow: 0 0 2px rgba(0, 0, 0, 0.06); color: #545454; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 16px; line-height: 1.5; margin: 0 auto; max-width: 800px; padding: 2em 2em 4em; } h1, h2, h3, h4, h5, h6 { color: #222; font-weight: 600; line-height: 1.3; } h2 { margin-top: 1.3em; } a { color: #0083e8; } b, strong { font-weight: 600; } samp { display: none; } img { animation: colorize 2s cubic-bezier(0, 0, .78, .36) 1; background: transparent; border: 10px solid rgba(0, 0, 0, 0.12); border-radius: 4px; display: block; margin: 1.3em auto; max-width: 95%; } @keyframes colorize { 0% { -webkit-filter: grayscale(100%); filter: grayscale(100%); } 100% { -webkit-filter: grayscale(0%); filter: grayscale(0%); } } ================================================ FILE: samples/root/Getting Started/index.html ================================================ GETTING STARTED WITH BRACKETS

GETTING STARTED WITH BRACKETS

This is your guide!

Welcome to Brackets, a modern open-source code editor that understands web design. It's a lightweight, yet powerful, code editor that blends visual tools into the editor so you get the right amount of help when you want it.

Brackets is a different type of editor. Brackets has some unique features like Quick Edit, Live Preview and others that you may not find in other editors. Brackets is written in JavaScript, HTML and CSS. That means that most of you using Brackets have the skills necessary to modify and extend the editor. In fact, we use Brackets every day to build Brackets. To learn more about how to use the key features, read on.

Projects in Brackets

In order to edit your own code using Brackets, you can just open the folder containing your files. Brackets treats the currently open folder as a "project"; features like Code Hints, Live Preview and Quick Edit only use files within the currently open folder.

Once you're ready to get out of this sample project and edit your own code, you can use the dropdown in the left sidebar to switch folders. Right now, the dropdown says "Getting Started" - that's the folder containing the file you're looking at right now. Click on the dropdown and choose "Open Folder…" to open your own folder. You can also use the dropdown later to switch back to folders you've opened previously, including this sample project.

Quick Edit for CSS and JavaScript

No more switching between documents and losing your context. When editing HTML, use the Cmd/Ctrl + E shortcut to open a quick inline editor that displays all the related CSS. Make a tweak to your CSS, hit ESC and you're back to editing HTML, or just leave the CSS rules open and they'll become part of your HTML editor. If you hit ESC outside of a quick inline editor, they'll all collapse. Quick Edit will also find rules defined in LESS and SCSS files, including nested rules.

Want to see it in action? Place your cursor on the tag above and press Cmd/Ctrl + E. You should see a CSS quick editor appear above, showing the CSS rule that applies to it. Quick Edit works in class and id attributes as well. You can use it with your LESS and SCSS files also. You can create new rules the same way. Click in one of the tags above and press Cmd/Ctrl + E. There are no rules for it right now, but you can click the New Rule button to add a new rule for . A screenshot showing CSS Quick Edit

You can use the same shortcut to edit other things as well - like functions in JavaScript, colors, and animation timing functions - and we're adding more and more all the time.

For now inline editors cannot be nested, so you can only use Quick Edit while the cursor is in a "full size" editor.

Preview HTML and CSS changes live in the browser

You know that "save/reload dance" we've been doing for years? The one where you make changes in your editor, hit save, switch to the browser and then refresh to finally see the result? With Brackets, you don't have to do that dance.

Brackets will open a live connection to your local browser and push HTML and CSS updates as you type! You might already be doing something like this today with browser-based tools, but with Brackets there is no need to copy and paste the final code back into the editor. Your code runs in the browser, but lives in your editor!

Live Highlight HTML elements and CSS rules

Brackets makes it easy to see how your changes in HTML and CSS will affect the page. When your cursor is on a CSS rule, Brackets will highlight all affected elements in the browser. Similarly, when editing an HTML file, Brackets will highlight the corresponding HTML elements in the browser.

If you have Google Chrome installed, you can try this out yourself. Click on the lightning bolt icon in the top right corner of your Brackets window or hit Cmd/Ctrl + Alt + P. When Live Preview is enabled on an HTML document, all linked CSS documents can be edited in real-time. The icon will change from gray to gold when Brackets establishes a connection to your browser. Now, place your cursor on the tag above. Notice the blue highlight that appears around the image in Chrome. Next, use Cmd/Ctrl + E to open up the defined CSS rules. Try changing the size of the border from 10px to 20px or change the background color from "transparent" to "hotpink". If you have Brackets and your browser running side-by-side, you will see your changes instantly reflected in your browser. Cool, right?

Today, Brackets only supports Live Preview for HTML and CSS. However, in the current version, changes to JavaScript files are automatically reloaded when you save. We are currently working on Live Preview support for JavaScript. Live previews are also only possible with Google Chrome, but we hope to bring this functionality to all major browsers in the future.

Quick View

For those of us who haven't yet memorized the color equivalents for HEX or RGB values, Brackets makes it quick and easy to see exactly what color is being used. In either CSS or HTML, simply hover over any color value or gradient and Brackets will display a preview of that color/gradient automatically. The same goes for images: simply hover over the image link in the Brackets editor and it will display a thumbnail preview of that image.

To try out Quick View for yourself, place your cursor on the tag at the top of this document and press Cmd/Ctrl + E to open a CSS quick editor. Now simply hover over any of the color values within the CSS. You can also see it in action on gradients by opening a CSS quick editor on the tag and hovering over any of the background image values. To try out the image preview, place your cursor over the screenshot image included earlier in this document.

Need something else? Try an extension!

In addition to all the goodness that's built into Brackets, our large and growing community of extension developers has built hundreds of extensions that add useful functionality. If there's something you need that Brackets doesn't offer, more than likely someone has built an extension for it. To browse or search the list of available extensions, choose File > Extension Manager… and click on the "Available" tab. When you find an extension you want, just click the "Install" button next to it.

Get involved

Brackets is an open-source project. Web developers from around the world are contributing to build a better code editor. Many more are building extensions that expand the capabilities of Brackets. Let us know what you think, share your ideas or contribute directly to the project.

================================================ FILE: samples/root/Getting Started/main.css ================================================ html { background: #e6e9e9; background-image: linear-gradient(270deg, rgb(230, 233, 233) 0%, rgb(216, 221, 221) 100%); -webkit-font-smoothing: antialiased; } body { background: #fff; box-shadow: 0 0 2px rgba(0, 0, 0, 0.06); color: #545454; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 16px; line-height: 1.5; margin: 0 auto; max-width: 800px; padding: 2em 2em 4em; } h1, h2, h3, h4, h5, h6 { color: #222; font-weight: 600; line-height: 1.3; } h2 { margin-top: 1.3em; } a { color: #0083e8; } b, strong { font-weight: 600; } samp { display: none; } img { animation: colorize 2s cubic-bezier(0, 0, .78, .36) 1; background: transparent; border: 10px solid rgba(0, 0, 0, 0.12); border-radius: 4px; display: block; margin: 1.3em auto; max-width: 95%; } @keyframes colorize { 0% { -webkit-filter: grayscale(100%); filter: grayscale(100%); } 100% { -webkit-filter: grayscale(0%); filter: grayscale(0%); } } ================================================ FILE: samples/ru/Getting Started/index.html ================================================ НАЧАЛО РАБОТЫ С BRACKETS

НАЧАЛО РАБОТЫ С BRACKETS

Ваше личное руководство!

Добро пожаловать в раннюю версию Brackets, нового редактора с открытым исходным кодом для веба следующего поколения. Мы большие фанаты стандартов и хотим построить лучший инструмент для JavaScript, HTML и CSS и связанных с ними открытых веб-технологий. Это наше скромное начало.

Во многих отношениях, Brackets необычный редактор. Одна примечательная особенность в том, что этот редактор написан на JavaScript, HTML and CSS. Это означает, что большинство пользователей Brackets имеют навыки необходимые для доработки и расширения редактора. На самом деле, мы используем Brackets каждый день для того, чтобы улучшать Brackets. Он так же имеет несколько особенностей вроде Быстрого Редактирования, Интерактивного Просмотра и других, которые вы не сможете найти в других редакторах. Читайте далее для того чтобы узнать, как использовать эти особенности редактора.

Мы испытываем несколько новых штук

Быстрое редактирование CSS и JavaScript

Теперь никакого переключения между документами и потери контекста. Во время редактирования HTML используйте сочетание клавиш Cmd/Ctrl + E для открытия быстрого редактора, который показывает все связанное с этой строкой CSS. Сделайте изменение CSS-стилей, нажмите ESC и вернитесь обратно к редактированию HTML. Или просто оставьте блок с CSS-правилами открытым, и они станут частью вашего HTML-редактора. Если вы нажмете ESC вне быстрого редактора, все CSS-правила закроются.

Хотите увидеть это в действии? Поставьте курсор на теге выше и нажмите Cmd/Ctrl + E. Вы должны увидеть, как выше появится быстрый редактор CSS. Справа вы увидите список CSS-правил, которые относятся к этому тегу. Просто прокрутите правила вниз, используя Alt + Up/Down, чтобы найти то, которое вы хотите отредактировать. A screenshot showing CSS Quick Edit

Вы так же можете использовать эти горячие клавиши при работе с кодом JavaScript, для того, чтобы увидеть содержание функции, просто наведите курсор на её название. На данный момент внутри встроенного редактора нельзя открыть еще один, поэтому вы можете использовать только Быстрое Редактирование, когда курсор находится в "полноэкранном" редакторе.

Просматривайте изменения CSS вживую в браузере!

Вы знаете эти пляски с "сохранить/перезагрузить", которые мы делаем годами? Когда вы делаете изменения в вашем редакторе, нажимаете сохранить, переключаетесь в браузер и затем нажимаете перезагрузить, чтобы наконец увидеть результат? Вместе с Brackets этого больше не придется делать.

Brackets откроет прямое соединение с вашим локальным браузером и направит ваши изменения CSS, как только вы их напечатаете! Вы, возможно, уже делали что-то подобное с инструментами, встроенными в браузер, но с Brackets больше нет нужды копировать и вставлять финальный CSS обратно в редактор. Ваш код запускается в браузере, но живет в вашем редакторе!

Подсвечивание HTML-элементов и CSS-правил в реальном времени

С Brackets стало проще понять, как изменения в HTML и CSS отразятся на странице. Когда ваш курсор находится на CSS-правиле, Brackets подсветит все затронутые элементы в браузере. То же самое и с редактированием HTML-файла, Brackets будет подсвечивать соответсвующие HTML-элементы в браузере.

Если у вас есть установленный Google Chrome, вы можете попробовать это сами. Нажмите на иконку молнии в правом верхнем углу или нажмите Cmd/Ctrl + Alt + P. Когда Интерактивный Просмотр включен в HTML-документе, все подключенные CSS-документы могут редактироваться в реальном времени. Иконка изменится с серой на золотую, когда Brackets установит соединение с вашим браузером. Теперь, поставьте курсор на теге выше и используйте Cmd/Ctrl + E, чтобы открыть записанные CSS-правила. Попробуйте изменить размер границы с 10 пикселя до 20 или изменить цвет фона с "transparent" на "hotpink". Если Brackets и ваш браузер работают вместе, вы увидите, как ваши изменения мгновенно отразятся в вашем браузере. Круто, правда?

Сегодня, Brackets поддерживает Интерактивный Просмотр только для CSS. Сейчас мы работаем над поддержкой Интерактивного Просмотра для HTML и JavaScript. В текущей версии вы не увидите изменений в вашем HTML- или JavaScript-файле до тех пор, пока не сохраните документ. Интерактивный Просмотр работает только с Google Chrome. Но в будушем мы планируем добавить эту возможность для всех основных браузеров.

Быстрый просмотр

Для тех из нас, кто до сих пор не запомнил значения цветов для HEX или RGB, Brackets позволяет быстро и просто посмотреть напрямую, какой цвет используется. В любом CSS- или HTML-файле, просто наведите курсор на значение цвета или градиента и Brackets автоматически отобразит этот цвет/градиент. То же самое и с изображениями: просто наведите курсор на ссылку с изображением в редакторе и Brackets выведет миниатюру этого изображения.

Попробуйте быстрый просмотр сами, поместите курсор на тэг вверху этого документа и нажмите Cmd/Ctrl + E для того, чтобы открыть быстрый редактор CSS. Сейчас просто наведите курсор на любое значение цвета в CSS. Вы так же можете увидеть это в действии с градиентом, открыв быстрый редактор CSS на тэге и наведя курсор на любое значение фонового рисунка. Попробуйте быстрый просмотр изображений, поместите ваш курсор на любой скриншот в этом документе.

Принимайте участие

Brackets — проект с открытым исходным кодом. Веб-разработчики со всех уголков мира способствуют созданию лучшего редактора кода. Многие разрабатывают дополнения, которые расширяют возможности Brackets. Расскажите нам, что вы думаете, поделитесь идеями или непосредственно поддержите проект.

================================================ FILE: samples/ru/Getting Started/main.css ================================================ html { background: #e6e9e9; background-image: linear-gradient(270deg, rgb(230, 233, 233) 0%, rgb(216, 221, 221) 100%); -webkit-font-smoothing: antialiased; } body { background: #fff; box-shadow: 0 0 2px rgba(0, 0, 0, 0.06); color: #545454; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 16px; line-height: 1.5; margin: 0 auto; max-width: 800px; padding: 2em 2em 4em; } h1, h2, h3, h4, h5, h6 { color: #222; font-weight: 600; line-height: 1.3; } h2 { margin-top: 1.3em; } a { color: #0083e8; } b, strong { font-weight: 600; } samp { display: none; } img { animation: colorize 2s cubic-bezier(0, 0, .78, .36) 1; background: transparent; border: 10px solid rgba(0, 0, 0, 0.12); border-radius: 4px; display: block; margin: 1.3em auto; max-width: 95%; } @keyframes colorize { 0% { -webkit-filter: grayscale(100%); filter: grayscale(100%); } 100% { -webkit-filter: grayscale(0%); filter: grayscale(0%); } } ================================================ FILE: samples/sv/Kom igang/index.html ================================================ KOM IGÅNG MED BRACKETS

KOM IGÅNG MED BRACKETS

Detta är din guide!

Välkommen till en tidig version av Brackets, en ny open-source editor för nästa generation av webben. Vi är hängivna anhängare av webbstandarder och vill bygga bättre verktyg för JavaScript, HTML och CSS samt relaterade öppna webbteknologier. Detta är vårt första ödmjuka steg.

Brackets är en annan typ av editor. En anmärkningsvärd skillnad är att denna editor är skriven i JavaScript, HTML och CSS. Detta innebär att de flesta som använder Brackets har kunskapen som krävs för att förändra och förbättra editorn. Faktum är att vi använder Brackets varje dag, för att bygga Brackets. Det har också ett antal unika funktioner som Quick Edit, Live Preview och och ytterligare några som du inte hittar i andra editorer. Läs vidare för att lära dig mer om dessa funktioner.

Vi provar en del nya saker

Quick Edit för CSS och JavaScript

Du behöver inte längre tappa sammanhanget när du flyttar mellan olika dokument. När du redigerar HTML kan du använda kortkommandot Cmd/Ctrl + E för att öppna en inline-editor som visar all relaterad CSS. Gör förändringen i din CSS, tryck på ESC och du är tillbaka i HTML. Du kan också lämna CSS-reglerna öppna och göra dem till en del av din HTML-editor. Om du trycker på ESC utanför en inline-editor döljs samtliga.

Vill du se hur det fungerar? Placera markören på -elementet ovan och tryck Cmd/Ctrl + E. Då visas CSS quick editorn ovan. Till höger kan du se en lista över alla CSS-regler som är relaterade till detta element. Det fungerar även på klass och ID-attribut. Du kan skapa nya regler på samma sätt. Klicka på en av -taggarna ovan och tryck Cmd/Ctrl + E. Just nu finns det inga regler men genom att klicka på knappen Ny regel skapar du en ny stilregel för -taggar. En skärmdump som visas CSS Quick Edit

Samma kortkommandon kan användas även på andra saker, till exempel funktioner i JavaScript för att ändra
färger, tidsfunktioner för animering och nya saker läggs till hela tiden! Just nu kan inte inline-editorer nästlas så du kan bara använda Quick Edit från den "fullstora" editorn.

Förhandsvisa CSS-ändringar direkt i webbläsaren

Du vet den där "spara och ladda om"-proceduren vi använt oss av i flera år? Den där du gör en ändring i din editor, sparar, går till webbläsaren och laddar om för att se resultatet? Med Brackets behöver du inte göra det.

Brackets öppnar en direktlänk till din lokala webbläsare och skjuter ut dina HTML- och CSS-ändringar medan du skriver! Du kanske redan använder något liknande webbläsarverktyg men med Brackets behöver du inte kopiera och klistra in koden fram och tillbaka mellan webbläsare och editor. Din kod körs i webbläsaren men skrivs i din editor!

Markera HTML-element och CSS-regler i realtid

Brackets gör det enkelt att se hur dina HTML- och CSS-ändringar kommer att påverka sidan. När din markör står på en CSS-regel markerar Brackets samtliga berörda element i webbläsaren. På samma sätt markerar Brackets respektive element i webbläsaren när du redigerar HTML-koden.

Om du har Google Chrome installerat kan du prova denna funktion själv. Klicka på blixtikonen i det övre högra hörnet i ditt Brackets-fönster eller använd kortkommandot Cmd/Ctrl + Alt + P. När Live Preview är aktiverat i ett HTML-dokument kommer alla länkade CSS-dokument att kunna redigeras i realtid. Ikonens färg kommer att byta färg från grå till guld när Brackets lyckats skapa en länk till din webbläsare. Om du sedan placerar markören på -taggen ovan ser du hur en blå markeringen visas runt bilden i Chrome. Du kan sedan använda Cmd/Ctrl + E för att visa de relaterade CSS-reglerna. Prova att ändra tjockleken på border-egenskapen från 10px till 20px eller att ändra backgrundsfärgen från "transparent" till "hotpink". Om Brackets och din webbläsare körs sida vid sida kommer du att se dina ändringar genomföras direkt i webbläsaren. Coolt va?

För tillfället stöder Brackets bara Live Preview för HTML och CSS. Dock laddas webbläsaren automatiskt när du sparar HTML- eller JavaScript-dokument. Vi jobbar för fullt med att utveckla stöd för Live Preview även för JavaScript. Live preview fungerar just nu bara i Google Chrome men med tiden hoppas vi kunna erbjuda denna funktionalitet i alla vanligt förekommande webbläsare.

Quick View

För de av oss som fortfarande inte memorerat färgkoderna för HEX eller RGB gör Brackets det snabbt och enkelt att se vilken färg som används. När du pekar på ett färgvärde eller gradient, i antingen HTML eller CSS, visas en förhandsgranskning av färgen/gradienten automatiskt. Detsamma gäller bilder: peka på bildens sökväg i Brackets så visas en tumnagelversion av bilden.

Du kan prova Quick View själv genom att placera markören på -taggen i början av detta dokument och trycka Cmd/Ctrl + E för att öppna snabbeditorn för CSS. När du pekar över ett färgvärde i CSS-koden visas motsvarande färg. Du kan utnyttja samma funktion med gradients i snabbeditorn - placera markören på -taggen och peka på dess background-image-egenskap. Du kan också prova förhandvisningen av bilder genom att placera markören vid skärmdumpen tidigare i detta dokument.

Behöver du någonting annat? Prova ett tillägg!

Utöver alla bra funktioner som är inbyggda i Brackets har vårt stora, och växande, community av tilläggsutvecklare tagit fram mer än hundra tillägg som ger mer användar funktionalitet. Om du saknar någonting i Brackets är det stor chans att att någon redan byggt ett tillägg för att lösa det. För att bläddra eller söka i listan över tillgängliga tillägg går du till Arkiv > Tilläggshanteraren och klickar på fliken "Tillgängliga". När du hittat ett tillägg du vill ha klickar du bara på knappen "Installera" intill det.

ENGAGERA DIG

Brackets är ett open-source-projekt. Webbutvecklare från hela världen bidrar för att göra Brackets till en bättre kodeditor. Många andra bygger tillägg som ökar Brackets funktionalitet. Berätta för oss vad du tycker, dina åsiker och idéer eller bidra med kod direkt till projektet.

================================================ FILE: samples/sv/Kom igang/main.css ================================================ html { background: #e6e9e9; background-image: linear-gradient(270deg, rgb(230, 233, 233) 0%, rgb(216, 221, 221) 100%); -webkit-font-smoothing: antialiased; } body { background: #fff; box-shadow: 0 0 2px rgba(0, 0, 0, 0.06); color: #545454; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 16px; line-height: 1.5; margin: 0 auto; max-width: 800px; padding: 2em 2em 4em; } h1, h2, h3, h4, h5, h6 { color: #222; font-weight: 600; line-height: 1.3; } h2 { margin-top: 1.3em; } a { color: #0083e8; } b, strong { font-weight: 600; } samp { display: none; } img { animation: colorize 2s cubic-bezier(0, 0, .78, .36) 1; background: transparent; border: 10px solid rgba(0, 0, 0, 0.12); border-radius: 4px; display: block; margin: 1.3em auto; max-width: 95%; } @keyframes colorize { 0% { -webkit-filter: grayscale(100%); filter: grayscale(100%); } 100% { -webkit-filter: grayscale(0%); filter: grayscale(0%); } } ================================================ FILE: samples/uk/Pochynayemo/index.html ================================================ ПОЧАТОК РОБОТИ З BRACKETS

ПОЧАТОК РОБОТИ З BRACKETS

Ваш власний посібник!

Ласкаво просимо у ранню версію Brackets, нового редактора з відкритим кодом для вебу наступного покоління. Ми великі фанати стандартів і хочемо побудувати кращий інструмент для JavaScript, HTML і CSS та пов'язаних з ними відкритих веб-технологій. Це наш скромний початок.

Багато у чому, Brackets - незвичайний редактор. Одна примітна особливість у тому, що цей редактор написаний на JavaScript, HTML та CSS. Це означає, що більшість користувачів Brackets мають необхідні навички для доопрацювання і розширення редактора. Насправді, ми використовуємо Brackets кожен день для того, щоб покращувати його. Він так само має декілька особливостей, таких як Швидке Редагування, Інтерактивний Перегляд та інших, які ви не зможете знайти у інших редакторах. Читайте далі для того, щоб дізнатися, як використовувати ці особливості редактора.

Ми випробуємо декілька нових штук

Швидке редагування CSS та JavaScript

Тепер ніякого перемикання між документами і втрати контексту. Під час редагування HTML використовуйте поєднання клавіш Cmd/Ctrl + E для відкриття швидкого редактора, який показує все пов'язане з цим рядком CSS. Зробіть зміну CSS-стилів, натисніть ESC і поверніться назад до редагування HTML. Або просто залиште блок з CSS-правил відкритим, і вони стануть частиною вашого HTML-редактора. Якщо ви натиснете ESC поза швидким редактором, усі CSS-правила закриються.

Хочете побачити це в дії? Поставте курсор на тезі вище і натисніть Cmd/Ctrl + E. Ви повинні побачити, як вище з'явиться швидкий редактор CSS. Праворуч ви побачите список CSS-правил, які відносяться до цього тегу. Просто прокрутіть правила вниз, використовуючи Alt + Up/Down, щоб знайти те, що ви хочете відредагувати. Скріншот який показує CSS Quick Edit

Ви так само можете використовувати ці гарячі клавіши при роботі з кодом JavaScript, для того, щоб побачити зміст функції, просто наведіть курсор на її назву. На даний момент всередині вбудованого редактора не можна відкрити ще один, тому ви можете використовувати тільки Швидке Редагування, коли курсор знаходиться у "повноекранному" редакторі.

Переглядайте зміни CSS наживо в браузері!

Ви знаєте цю мороку зі "зберегти / перезавантажити", яку ми робимо роками? Коли ви робите зміни у вашому редакторі, натискаєте зберегти, перемикаєтеся у браузер і потім натискаєте перезавантажити, щоб нарешті побачити результат? Разом з Brackets цього більше не доведеться робити.

Brackets відкриє пряме з'єднання з вашим локальним браузером та направить ваші зміни CSS, як тільки ви їх надрукуєте! Ви, можливо, вже робили щось подібне з інструментами, вбудованими в браузер, але з Brackets більше немає потреби копіювати та вставляти фінальний CSS назад у редактор. Ваш код запускається у браузері, але живе у вашому редакторі!

Підсвічування HTML-елементів і CSS-правил у реальному часі

З Brackets стало простіше зрозуміти, як зміни в HTML і CSS позначаться на сторінці. Коли ваш курсор знаходиться на CSS-правилі, Brackets підсвітить всі його елементи в браузері. Те ж саме і з редагуванням HTML-файлу, Brackets буде підсвічувати відповідні HTML-елементи у браузері.

Якщо у вас є встановлений Google Chrome, ви можете спробувати це самі. Натисніть на іконку блискавки у правому верхньому кутку або натисніть Cmd/Ctrl + Alt + P. Коли Інтерактивний Перегляд включений у HTML-документі, всі підключені CSS-документи можуть редагуватися у реальному часі. Іконка зміниться з сірої на золоту, коли Brackets встановить з'єднання з вашим браузером. Тепер, поставте курсор на тезі вище і використовуйте Cmd/Ctrl + E, щоб відкрити записані CSS-правила. Спробуйте змінити розмір межі з 10 пікселів до 20, або змінити колір фону з "transparent" на "hotpink". Якщо Brackets і ваш браузер працюють разом, ви побачите, як ваші зміни миттєво з'являться у вашому браузері. Круто, правда?

Сьогодні, Brackets підтримує Інтерактивний Перегляд тільки для CSS. Зараз ми працюємо над підтримкою Інтерактивного Перегляду для HTML та JavaScript. У поточній версії ви не побачите змін в вашому HTML- або JavaScript-файлі до тих пір, поки не збережете документ. Інтерактивний Перегляд працює тільки з Google Chrome. Але в майбутньому ми плануємо додати цю можливість для всіх основних браузерів.

Швидкий перегляд

Для тих з нас, хто до цієї пори не запам'ятав значення кольорів для HEX або RGB, Brackets дозволяє швидко і просто подивитися безпосередньо, який колір використовується. У будь-якому CSS- або HTML-файлі, просто наведіть курсор на значення кольору або градієнта і Brackets автоматично відобразить цей колір / градієнт. Те ж саме і з зображеннями: просто наведіть курсор на посилання з зображенням у редакторі і Brackets виведе мініатюру цього зображення.

Спробуйте швидкий перегляд самі, помістіть курсор на тег вгорі цього документа і натисніть Cmd/Ctrl + E для того, щоб відкрити швидкий редактор CSS. Зараз просто наведіть курсор на будь-яке значення кольору в CSS. Ви так само можете побачити це в дії з градієнтом, відкривши швидкий редактор CSS на тезі та навівши курсор на будь-яке значення фонового малюнка. Спробуйте швидкий перегляд зображень, помістіть ваш курсор на будь-який скріншот у цьому документі.

Приймайте участь

Brackets — проект з відкритим кодом. Веб-розробники з усіх куточків світу сприяють створення кращого редактора коду. Багато розробляють доповнення, які розширюють можливості Brackets. Розкажіть нам, що ви думаєте, поділіться ідеями або безпосередньо підтримайте проект.

================================================ FILE: samples/uk/Pochynayemo/main.css ================================================ html { background: #e6e9e9; background-image: linear-gradient(270deg, rgb(230, 233, 233) 0%, rgb(216, 221, 221) 100%); -webkit-font-smoothing: antialiased; } body { background: #fff; box-shadow: 0 0 2px rgba(0, 0, 0, 0.06); color: #545454; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 16px; line-height: 1.5; margin: 0 auto; max-width: 800px; padding: 2em 2em 4em; } h1, h2, h3, h4, h5, h6 { color: #222; font-weight: 600; line-height: 1.3; } h2 { margin-top: 1.3em; } a { color: #0083e8; } b, strong { font-weight: 600; } samp { display: none; } img { animation: colorize 2s cubic-bezier(0, 0, .78, .36) 1; background: transparent; border: 10px solid rgba(0, 0, 0, 0.12); border-radius: 4px; display: block; margin: 1.3em auto; max-width: 95%; } @keyframes colorize { 0% { -webkit-filter: grayscale(100%); filter: grayscale(100%); } 100% { -webkit-filter: grayscale(0%); filter: grayscale(0%); } } ================================================ FILE: samples/zh-cn/Getting Started/index.html ================================================ BRACKETS 入门

BRACKETS 入门

让我为你好好介绍!

欢迎使用 Brackets,这是个很懂网页设计的现代化开放原始码程式编辑器。 轻巧又不失威力,整合多项视觉化的编辑功能,在需要时提供您适当的协助。

Brackets 与众不同。 Brackets 提供「快速编辑」、「即时预览」等别的编辑器没有的独家功能。 而且 Brackets 是用 JavaScript, HTML 及 CSS 写出来的。这代表大多数使用 Brackets 的人都有能力修改及扩充它。 事实上,Brackets 本身就是我们用 Brackets 一天天打造出来的。 如果您想学会如何使用这些功能,请继续看下去。

Brackets 中的「专案」

只要开启包含您程式码的资料夹,就能使用 Brackets 来编辑。 Brackets 会将目前开启的资料夹视为一个「专案」,「程式提示」、「即时预览」及「快速编辑」等功能都只会参考到专案裡的档案。

要是您已经准备好关掉这个范例专案,开始编辑自已的程式,可以使用左边侧栏的下拉式选单切换资料夹。 现在应该是选到「Getting Started」,也就是您看的这份文件所在的资料夹。 按一下下拉式选单,点选「开启资料夹…」选项,就能开启您自已的资料夹。 之后您也可以透过同样的下拉式选单切回开启过的资料夹,包含这个范例专案。

CSS 及 JavaScript 快速编辑

别再因为不断切换档案而一直分神失焦了。编辑 HTML 时,按下 Cmd/Ctrl + E 快速键就地开启编辑器,秀出所有相关的 CSS 规则。 调好 CSS 样式后按 ESC 马上就能回到 HTML 继续编辑。 此外,也可以放手让那些 CSS 规则一直开在 HTML 编辑器裡。 只要在快速编辑器的范围外按下 ESC 键,就能关掉所有快速编辑器。 快速编辑也能找到定义在 LESS 及 SCSS 档案中的规则,就算是巢状规则也没问题。

想亲身体验吗? 把游标移到上面的 标籤中,按下 Cmd/Ctrl + E。 您应该就会看到 CSS 快速编辑器出现在上方,显示出所有套用到的 CSS 规则。 快速编辑功能也支援 class 及 id 属性。搭配 LESS 或 SCSS 档嘛会通喔。 您也可以透过这个方式新增规则。在上方随便一个 标籤上点一下,按 Cmd/Ctrl + E。 可以看到它上面并没有任何 CSS 规则,但您可以按一下「新增规则」按钮,就会新增 规则。 使用 CSS 快速编辑的画面撷图

您也能使用相同的快速键编辑其他东西,例如 JavaScript 函式、CSS 色彩、CSS 动画计时函式等,持续增加中。

目前还不能在快速编辑器中巢状开启其他快速编辑器,只有游标在主编辑器时才能开快速编辑功能。

在浏览器裡即时预览 HTML 及 CSS 变更

有一种舞叫做「存档再重新载入探戈」,我们跳了好多年,您听过吗? 就是在编辑器裡改一改东西,储存好,马上再切过去浏览器,按「重新整理」后才能真正的看到结果,超鸟的! 用 Brackets,您永远不必再这麽「跳」。

Brackets 会跟您本机的浏览器即时连线,在您修改的同时将 HTML 及 CSS 内容更新过去! 说不定活在 21 世纪的您已经用浏览器提供的开发者工具做过类似的事了。 但是用 Brackets,您不用再手动把总算是会动的程式複製贴回编辑器。 您的程式虽然是跑在浏览器上,但是所有的血与肉都还是在编辑器裡啊!

即时突显 HTML 元素及 CSS 规则

Brackets 让您更容易看到 HTML 及 CSS 的修改会对页面造成什麽影响。 当游标停在 CSS 规则上时,Brackets 会在浏览器裡将所有会受影响的元素突显出来。 编辑 HTML 档案时,Brackets 也会在浏览器中突显对应的 HTML 元素。

如果您安装了 Google Chrome,马上就可以试看看。 按一下 Brackets 视窗右上角的闪电图示,或是按 Cmd/Ctrl + Alt + P。 当即时预览功能在 HTML 档案上启用后,所有连结到的 CSS 档案也都可以马上编辑马上生效。 Brackets 与您的浏览器建立连线时,图示会由灰转金。 就是现在,把游标移到上面的 标籤。注意看 Chrome 在图片上显示的蓝色框。 接下来,按 Cmd/Ctrl + E 开启相关的 CSS 规则定义。 试著将框线 (border) 值由 10px 改成 20px,或将背景色 (background-color) 由透明 "transparent" 改成 "hotpink"。 如果您把 Brackets 跟浏览器并排放好,就能看到所有异动都直接反应在浏览器上了。酷吧?!

目前 Brackets 只能即时预览 HTML 及 CSS。不过,储存修改过的 JavaScript 档案时也会自动重新载入页面。 我们正在努力让即时预览功能支援 JavaScript。 此外,即时预览现在只能在 Google Chrome 上执行,我们希望将来能支援所有主流的浏览器。

快速检视

为了那些记不得色彩十六进位值或是 RGB 值的人,Brackets 能快速又简单的让您看见色彩的真相。 不管在 CSS 或 HTML 中,只要将滑鼠游标移到任何色彩值或是渐变色上,Brackets 就会自动显示预览。 对图片也同样有用,在 Brackets 裡将滑鼠游标移到图片连结上,就会自动显示预览缩图。

自已试试快速检视,只要将游标移到这份文件最上方的 标籤上,按下 Cmd/Ctrl + E 开启 CSS 快速编辑器,将滑鼠游标移到 CSS 上的任何一个色彩值上就能看到。 想要预览渐变色,您也可以在 标籤上开启 CSS 快速编辑器,移到随便一个背景图片 (background-image) 值就能看到。 要试图片预览,则是将游标移到前几段提到的画面撷图上就能看到。

还不够吗? 安装扩充功能吧!

除了 Brackets 内建的这些好物外,我们那深具规模,且日益状大的开发者社群已经写出了数百个扩充功能。 如果您觉得 Brackets 少了什麽,说不定早就有人写好扩充功能了。 点一下 档案 > 扩充功能管理员...,再点一下「可使用」页籤,就能浏览或搜寻扩充功能清单。 一旦找到想要的扩充功能,按一下后面的「安装」按钮就可以了。

一起参与

Brackets 专案是开放原始码的。世界各地的网页开发者贡献一己之力,只为打造出更好的程式编辑器。 也有不少人正在开发扩充功能,让 Brackets 更强大。 告诉我们您的想法,分享您的构想,或是直接为本专案做点事吧。

================================================ FILE: samples/zh-cn/Getting Started/main.css ================================================ html { background: #e6e9e9; background-image: linear-gradient(270deg, rgb(230, 233, 233) 0%, rgb(216, 221, 221) 100%); -webkit-font-smoothing: antialiased; } body { background: #fff; box-shadow: 0 0 2px rgba(0, 0, 0, 0.06); color: #545454; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 16px; line-height: 1.5; margin: 0 auto; max-width: 800px; padding: 2em 2em 4em; } h1, h2, h3, h4, h5, h6 { color: #222; font-weight: 700; line-height: 1.3; } h2 { margin-top: 1.3em; } a { color: #0083e8; } b, strong { font-weight: 700; } samp { display: none; } img { animation: colorize 2s cubic-bezier(0, 0, .78, .36) 1; background: transparent; border: 10px solid rgba(0, 0, 0, 0.12); border-radius: 4px; display: block; margin: 1.3em auto; max-width: 95%; } @keyframes colorize { 0% { -webkit-filter: grayscale(100%); filter: grayscale(100%); } 100% { -webkit-filter: grayscale(0%); filter: grayscale(0%); } } ================================================ FILE: samples/zh-tw/Getting Started/index.html ================================================ BRACKETS 入門

BRACKETS 入門

讓我為您好好介紹!

歡迎使用 Brackets,這是個很懂網頁設計的現代化開放原始碼程式編輯器。 輕巧又不失威力,整合多項視覺化的編輯功能,在需要時提供您適當的協助。

Brackets 與眾不同。 Brackets 提供「快速編輯」、「即時預覽」等別的編輯器沒有的獨家功能。 而且 Brackets 是用 JavaScript, HTML 及 CSS 寫出來的。這代表大多數使用 Brackets 的人都有能力修改及擴充它。 事實上,Brackets 本身就是我們用 Brackets 一天天打造出來的。 如果您想學會如何使用這些功能,請繼續看下去。

Brackets 中的「專案」

只要開啟包含您程式碼的資料夾,就能使用 Brackets 來編輯。 Brackets 會將目前開啟的資料夾視為一個「專案」,「程式提示」、「即時預覽」及「快速編輯」等功能都只會參考到專案裡的檔案。

要是您已經準備好關掉這個範例專案,開始編輯自已的程式,可以使用左邊側欄的下拉式選單切換資料夾。 現在應該是選到「Getting Started」,也就是您看的這份文件所在的資料夾。 按一下下拉式選單,點選「開啟資料夾…」選項,就能開啟您自已的資料夾。 之後您也可以透過同樣的下拉式選單切回開啟過的資料夾,包含這個範例專案。

CSS 及 JavaScript 快速編輯

別再因為不斷切換檔案而一直分神失焦了。編輯 HTML 時,按下 Cmd/Ctrl + E 快速鍵就地開啟編輯器,秀出所有相關的 CSS 規則。 調好 CSS 樣式後按 ESC 馬上就能回到 HTML 繼續編輯。 此外,也可以放手讓那些 CSS 規則一直開在 HTML 編輯器裡。 只要在快速編輯器的範圍外按下 ESC 鍵,就能關掉所有快速編輯器。 快速編輯也能找到定義在 LESS 及 SCSS 檔案中的規則,就算是巢狀規則也沒問題。

想親身體驗嗎? 把游標移到上面的 標籤中,按下 Cmd/Ctrl + E。 您應該就會看到 CSS 快速編輯器出現在上方,顯示出所有套用到的 CSS 規則。 快速編輯功能也支援 class 及 id 屬性。搭配 LESS 或 SCSS 檔嘛會通喔。 您也可以透過這個方式新增規則。在上方隨便一個 標籤上點一下,按 Cmd/Ctrl + E。 可以看到它上面並沒有任何 CSS 規則,但您可以按一下「新增規則」按鈕,就會新增 規則。 使用 CSS 快速編輯的畫面擷圖

您也能使用相同的快速鍵編輯其他東西,例如 JavaScript 函式、CSS 色彩、CSS 動畫計時函式等,持續增加中。

目前還不能在快速編輯器中巢狀開啟其他快速編輯器,只有游標在主編輯器時才能開快速編輯功能。

在瀏覽器裡即時預覽 HTML 及 CSS 變更

有一種舞叫做「存檔再重新載入探戈」,我們跳了好多年,您聽過嗎? 就是在編輯器裡改一改東西,儲存好,馬上再切過去瀏覽器,按「重新整理」後才能真正的看到結果,超鳥的! 用 Brackets,您永遠不必再這麼「跳」。

Brackets 會跟您本機的瀏覽器即時連線,在您修改的同時將 HTML 及 CSS 內容更新過去! 說不定活在 21 世紀的您已經用瀏覽器提供的開發者工具做過類似的事了。 但是用 Brackets,您不用再手動把總算是會動的程式複製貼回編輯器。 您的程式雖然是跑在瀏覽器上,但是所有的血與肉都還是在編輯器裡啊!

即時突顯 HTML 元素及 CSS 規則

Brackets 讓您更容易看到 HTML 及 CSS 的修改會對頁面造成什麼影響。 當游標停在 CSS 規則上時,Brackets 會在瀏覽器裡將所有會受影響的元素突顯出來。 編輯 HTML 檔案時,Brackets 也會在瀏覽器中突顯對應的 HTML 元素。

如果您安裝了 Google Chrome,馬上就可以試看看。 按一下 Brackets 視窗右上角的閃電圖示,或是按 Cmd/Ctrl + Alt + P。 當即時預覽功能在 HTML 檔案上啟用後,所有連結到的 CSS 檔案也都可以馬上編輯馬上生效。 Brackets 與您的瀏覽器建立連線時,圖示會由灰轉金。 就是現在,把游標移到上面的 標籤。注意看 Chrome 在圖片上顯示的藍色框。 接下來,按 Cmd/Ctrl + E 開啟相關的 CSS 規則定義。 試著將框線 (border) 值由 10px 改成 20px,或將背景色 (background-color) 由透明 "transparent" 改成 "hotpink"。 如果您把 Brackets 跟瀏覽器並排放好,就能看到所有異動都直接反應在瀏覽器上了。酷吧?!

目前 Brackets 只能即時預覽 HTML 及 CSS。不過,儲存修改過的 JavaScript 檔案時也會自動重新載入頁面。 我們正在努力讓即時預覽功能支援 JavaScript。 此外,即時預覽現在只能在 Google Chrome 上執行,我們希望將來能支援所有主流的瀏覽器。

快速檢視

為了那些記不得色彩十六進位值或是 RGB 值的人,Brackets 能快速又簡單的讓您看見色彩的真相。 不管在 CSS 或 HTML 中,只要將滑鼠游標移到任何色彩值或是漸變色上,Brackets 就會自動顯示預覽。 對圖片也同樣有用,在 Brackets 裡將滑鼠游標移到圖片連結上,就會自動顯示預覽縮圖。

自已試試快速檢視,只要將游標移到這份文件最上方的 標籤上,按下 Cmd/Ctrl + E 開啟 CSS 快速編輯器,將滑鼠游標移到 CSS 上的任何一個色彩值上就能看到。 想要預覽漸變色,您也可以在 標籤上開啟 CSS 快速編輯器,移到隨便一個背景圖片 (background-image) 值就能看到。 要試圖片預覽,則是將游標移到前幾段提到的畫面擷圖上就能看到。

還不夠嗎? 安裝擴充功能吧!

除了 Brackets 內建的這些好物外,我們那深具規模,且日益狀大的開發者社群已經寫出了數百個擴充功能。 如果您覺得 Brackets 少了什麼,說不定早就有人寫好擴充功能了。 點一下 檔案 > 擴充功能管理員...,再點一下「可使用」頁籤,就能瀏覽或搜尋擴充功能清單。 一旦找到想要的擴充功能,按一下後面的「安裝」按鈕就可以了。

一起參與

Brackets 專案是開放原始碼的。世界各地的網頁開發者貢獻一己之力,只為打造出更好的程式編輯器。 也有不少人正在開發擴充功能,讓 Brackets 更強大。 告訴我們您的想法,分享您的構想,或是直接為本專案做點事吧。

================================================ FILE: samples/zh-tw/Getting Started/main.css ================================================ html { background: #e6e9e9; background-image: linear-gradient(270deg, rgb(230, 233, 233) 0%, rgb(216, 221, 221) 100%); -webkit-font-smoothing: antialiased; } body { background: #fff; box-shadow: 0 0 2px rgba(0, 0, 0, 0.06); color: #545454; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 16px; line-height: 1.5; margin: 0 auto; max-width: 800px; padding: 2em 2em 4em; } h1, h2, h3, h4, h5, h6 { color: #222; font-weight: 700; line-height: 1.3; } h2 { margin-top: 1.3em; } a { color: #0083e8; } b, strong { font-weight: 700; } samp { display: none; } img { animation: colorize 2s cubic-bezier(0, 0, .78, .36) 1; background: transparent; border: 10px solid rgba(0, 0, 0, 0.12); border-radius: 4px; display: block; margin: 1.3em auto; max-width: 95%; } @keyframes colorize { 0% { -webkit-filter: grayscale(100%); filter: grayscale(100%); } 100% { -webkit-filter: grayscale(0%); filter: grayscale(0%); } } ================================================ FILE: src/JSUtils/HintUtils.js ================================================ /* * Copyright (c) 2013 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /*jslint regexp: true */ define(function (require, exports, module) { "use strict"; var Acorn = require("node_modules/acorn/dist/acorn"); var LANGUAGE_ID = "javascript", JSX_LANGUAGE_ID = "jsx", HTML_LANGUAGE_ID = "html", PHP_LANGUAGE_ID = "php", SUPPORTED_LANGUAGES = [LANGUAGE_ID, JSX_LANGUAGE_ID, HTML_LANGUAGE_ID, PHP_LANGUAGE_ID], SINGLE_QUOTE = "'", DOUBLE_QUOTE = "\""; /** * Create a hint token with name value that occurs at the given list of * positions. * * @param {string} value - name of the new hint token * @param {?Array.=} positions - optional list of positions at which * the token occurs * @return {Object} - a new hint token */ function makeToken(value, positions) { positions = positions || []; return { value: value, positions: positions }; } /** * Is the string key perhaps a valid JavaScript identifier? * * @param {string} key - string to test. * @return {boolean} - could key be a valid identifier? */ function maybeIdentifier(key) { var result = false, i; for (i = 0; i < key.length; i++) { result = Acorn.isIdentifierChar(key.charCodeAt(i)); if (!result) { break; } } return result; } /** * Is the token's class hintable? (A very conservative test.) * * @param {Object} token - the token to test for hintability * @return {boolean} - could the token be hintable? */ function hintable(token) { function _isInsideRegExp(token) { return token.state && (token.state.lastType === "regexp" || (token.state.localState && token.state.localState.lastType === "regexp")); } switch (token.type) { case "comment": case "number": case "regexp": case "string": case "def": // exclude variable & param decls return false; case "string-2": // exclude strings inside a regexp return !_isInsideRegExp(token); default: return true; } } /** * Determine if hints should be displayed for the given key. * * @param {string} key - key entered by the user * @param {boolean} showOnDot - show hints on dot ("."). * @return {boolean} true if the hints should be shown for the key, * false otherwise. */ function hintableKey(key, showOnDot) { return (key === null || (showOnDot && key === ".") || maybeIdentifier(key)); } /* * Get a JS-hints-specific event name. Used to prevent event namespace * pollution. * * @param {string} name - the unqualified event name * @return {string} - the qualified event name */ function eventName(name) { var EVENT_TAG = "brackets-js-hints"; return name + "." + EVENT_TAG; } /* * Annotate a list of tokens as literals of a particular kind; * if string literals, annotate with an appropriate delimiter. * * @param {Array.} literals - list of hint tokens * @param {string} kind - the kind of literals in the list (e.g., "string") * @return {Array.} - the input array; to each object in the array a * new literal {boolean} property has been added to indicate that it * is a literal hint, and also a new kind {string} property to indicate * the literal kind. For string literals, a delimiter property is also * added to indicate what the default delimiter should be (viz. a * single or double quotation mark). */ function annotateLiterals(literals, kind) { return literals.map(function (t) { t.literal = true; t.kind = kind; t.origin = "ecmascript"; if (kind === "string") { if (/[^\\]"/.test(t.value)) { t.delimiter = SINGLE_QUOTE; } else { t.delimiter = DOUBLE_QUOTE; } } return t; }); } /* * Annotate a list of tokens as keywords * * @param {Array.} keyword - list of keyword tokens * @return {Array.} - the input array; to each object in the array a * new keyword {boolean} property has been added to indicate that the * hint is a keyword. */ function annotateKeywords(keywords) { return keywords.map(function (t) { t.keyword = true; t.origin = "ecmascript"; return t; }); } function isSupportedLanguage(languageId) { return SUPPORTED_LANGUAGES.indexOf(languageId) !== -1; } var KEYWORD_NAMES = [ "break", "case", "catch", "class", "const", "continue", "debugger", "default", "delete", "do", "else", "export", "extends", "finally", "for", "function", "if", "import", "in", "instanceof", "let", "new", "return", "super", "switch", "this", "throw", "try", "typeof", "var", "void", "while", "with", "yield" ], KEYWORD_TOKENS = KEYWORD_NAMES.map(function (t) { return makeToken(t, []); }), KEYWORDS = annotateKeywords(KEYWORD_TOKENS); var LITERAL_NAMES = [ "true", "false", "null" ], LITERAL_TOKENS = LITERAL_NAMES.map(function (t) { return makeToken(t, []); }), LITERALS = annotateLiterals(LITERAL_TOKENS); exports.makeToken = makeToken; exports.hintable = hintable; exports.hintableKey = hintableKey; exports.maybeIdentifier = maybeIdentifier; exports.eventName = eventName; exports.annotateLiterals = annotateLiterals; exports.isSupportedLanguage = isSupportedLanguage; exports.KEYWORDS = KEYWORDS; exports.LITERALS = LITERALS; exports.LANGUAGE_ID = LANGUAGE_ID; exports.SINGLE_QUOTE = SINGLE_QUOTE; exports.DOUBLE_QUOTE = DOUBLE_QUOTE; exports.SUPPORTED_LANGUAGES = SUPPORTED_LANGUAGES; }); ================================================ FILE: src/JSUtils/MessageIds.js ================================================ /* * Copyright (c) 2013 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ define(function (require, exports, module) { "use strict"; var TERN_ADD_FILES_MSG = "AddFiles", TERN_UPDATE_FILE_MSG = "UpdateFile", TERN_INIT_MSG = "Init", TERN_JUMPTODEF_MSG = "JumptoDef", TERN_COMPLETIONS_MSG = "Completions", TERN_GET_FILE_MSG = "GetFile", TERN_SCOPEDATA_MSG = "ScopeData", TERN_CALLED_FUNC_TYPE_MSG = "FunctionType", TERN_PRIME_PUMP_MSG = "PrimePump", TERN_GET_GUESSES_MSG = "GetGuesses", TERN_WORKER_READY = "WorkerReady", TERN_INFERENCE_TIMEDOUT = "InferenceTimedOut", SET_CONFIG = "SetConfig", TERN_UPDATE_DIRTY_FILE = "UpdateDirtyFileEntry", TERN_REFS = "getRefs", TERN_CLEAR_DIRTY_FILES_LIST = "ClearDirtyFilesList"; // Message parameter constants var TERN_FILE_INFO_TYPE_PART = "part", TERN_FILE_INFO_TYPE_FULL = "full", TERN_FILE_INFO_TYPE_EMPTY = "empty"; exports.TERN_ADD_FILES_MSG = TERN_ADD_FILES_MSG; exports.TERN_JUMPTODEF_MSG = TERN_JUMPTODEF_MSG; exports.TERN_COMPLETIONS_MSG = TERN_COMPLETIONS_MSG; exports.TERN_INIT_MSG = TERN_INIT_MSG; exports.TERN_GET_FILE_MSG = TERN_GET_FILE_MSG; exports.TERN_SCOPEDATA_MSG = TERN_SCOPEDATA_MSG; exports.TERN_CALLED_FUNC_TYPE_MSG = TERN_CALLED_FUNC_TYPE_MSG; exports.TERN_PRIME_PUMP_MSG = TERN_PRIME_PUMP_MSG; exports.TERN_GET_GUESSES_MSG = TERN_GET_GUESSES_MSG; exports.TERN_UPDATE_FILE_MSG = TERN_UPDATE_FILE_MSG; exports.TERN_WORKER_READY = TERN_WORKER_READY; exports.TERN_FILE_INFO_TYPE_PART = TERN_FILE_INFO_TYPE_PART; exports.TERN_FILE_INFO_TYPE_FULL = TERN_FILE_INFO_TYPE_FULL; exports.TERN_FILE_INFO_TYPE_EMPTY = TERN_FILE_INFO_TYPE_EMPTY; exports.TERN_INFERENCE_TIMEDOUT = TERN_INFERENCE_TIMEDOUT; exports.SET_CONFIG = SET_CONFIG; exports.TERN_UPDATE_DIRTY_FILE = TERN_UPDATE_DIRTY_FILE; exports.TERN_CLEAR_DIRTY_FILES_LIST = TERN_CLEAR_DIRTY_FILES_LIST; exports.TERN_REFS = TERN_REFS; }); ================================================ FILE: src/JSUtils/Preferences.js ================================================ /* * Copyright (c) 2013 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /** This class parses configuration values from a JSON object. The expected name of the file is “.jscodehints” but this class does not actually read the file, it just provides a constant, FILE_NAME. The following properties are supported: "excluded-directories" - An array of directory strings that match directories that will be excluded from analysis. Directories may be excluded if they contain automated tests that aren’t relevant for code hinting. The wildcards “*” and “?” are supported in strings. "excluded-files" - An array of file strings that match files that will be excluded from analysis. Files are typically excluded because their API is in a JSON file or they are known to cause problems with either stability or performance. The wildcards “*” and “?” are supported in strings. "max-file-count" - Limits the total number of files that can be processed for hints. "max-file-size" - Files larger than this number of bytes will not be parsed. The strings in "excluded-directories" or "excluded-files" will be treated as a regular expression if the first and last characters of the string are the '/' character. Note the '\' character in a regular expression needs to be escaped to be valid in a JSON formatted file. For example "/[\d]/" becomes "/[\\d]/". Example file: { "excluded-directories" : ["/ex[\\w]*ed/"], "excluded-files" : ["require.js", "jquery*.js", "less*.min.js", "ember*.js", "d2?.js", "d3*"], "max-file-count": 100, "max-file-size": 524288 } */ /*jslint regexp: true */ define(function (require, exports, module) { "use strict"; var StringUtils = require("utils/StringUtils"); /** * Convert an array of strings with optional wildcards, to an equivalent * regular expression. * * @param {Array.} settings from the file (note: this may be mutated by this function) * @param {?RegExp} baseRegExp - base regular expression that is always used * @param {?RegExp} defaultRegExp - additional regular expression that is only used if the user has not configured settings * @return {RegExp} Regular expression that captures the array of string * with optional wildcards. */ function settingsToRegExp(settings, baseRegExp, defaultRegExp) { var regExpString = ""; if (settings instanceof Array && settings.length > 0) { // Append base settings to user settings. The base // settings are builtin and cannot be overridden. if (baseRegExp) { settings.push("/" + baseRegExp.source + "/"); } // convert each string, with optional wildcards to an equivalent // string in a regular expression. settings.forEach(function (value, index) { if (typeof value === "string") { var isRegExp = value[0] === '/' && value[value.length - 1] === '/'; if (isRegExp) { value = value.substring(1, value.length - 1); } else { value = StringUtils.regexEscape(value); // convert user input wildcard, "*" or "?", to a regular // expression. We can just replace the escaped "*" or "?" // since we know it is a wildcard. value = value.replace("\\?", ".?"); value = value.replace("\\*", ".*"); // Add "^" and "$" to prevent matching in the middle of strings. value = "^" + value + "$"; } if (index > 0) { regExpString += "|"; } regExpString = regExpString.concat(value); } }); } if (!regExpString) { var defaultParts = []; if (baseRegExp) { defaultParts.push(baseRegExp.source); } if (defaultRegExp) { defaultParts.push(defaultRegExp.source); } if (defaultParts.length > 0) { regExpString = defaultParts.join("|"); } else { return null; } } return new RegExp(regExpString); } /** * Constructor to create a default preference object. * * @constructor * @param {Object=} prefs - preference object */ function Preferences(prefs) { var BASE_EXCLUDED_DIRECTORIES = null, /* if the user has settings, we don't exclude anything by default */ // exclude node_modules for performance reasons and because we don't do full hinting for those anyhow. DEFAULT_EXCLUDED_DIRECTORIES = /node_modules/, // exclude require and jquery since we have special knowledge of those BASE_EXCLUDED_FILES = /^require.*\.js$|^jquery.*\.js$/, DEFAULT_MAX_FILE_COUNT = 100, DEFAULT_MAX_FILE_SIZE = 512 * 1024; if (prefs) { this._excludedDirectories = settingsToRegExp(prefs["excluded-directories"], BASE_EXCLUDED_DIRECTORIES, DEFAULT_EXCLUDED_DIRECTORIES); this._excludedFiles = settingsToRegExp(prefs["excluded-files"], BASE_EXCLUDED_FILES); this._maxFileCount = prefs["max-file-count"]; this._maxFileSize = prefs["max-file-size"]; // sanity check values if (!this._maxFileCount || this._maxFileCount < 0) { this._maxFileCount = DEFAULT_MAX_FILE_COUNT; } if (!this._maxFileSize || this._maxFileSize < 0) { this._maxFileSize = DEFAULT_MAX_FILE_SIZE; } } else { this._excludedDirectories = DEFAULT_EXCLUDED_DIRECTORIES; this._excludedFiles = BASE_EXCLUDED_FILES; this._maxFileCount = DEFAULT_MAX_FILE_COUNT; this._maxFileSize = DEFAULT_MAX_FILE_SIZE; } } Preferences.FILE_NAME = ".jscodehints"; /** * Get the regular expression for excluded directories. * * @return {?RegExp} Regular expression matching the directories that should * be excluded. Returns null if no directories are excluded. */ Preferences.prototype.getExcludedDirectories = function () { return this._excludedDirectories; }; /** * Get the regular expression for excluded files. * * @return {?RegExp} Regular expression matching the files that should * be excluded. Returns null if no files are excluded. */ Preferences.prototype.getExcludedFiles = function () { return this._excludedFiles; }; /** * Get the maximum number of files that will be analyzed. * * @return {number} */ Preferences.prototype.getMaxFileCount = function () { return this._maxFileCount; }; /** * Get the maximum size of a file that will be analyzed. Files that are * larger will be ignored. * * @return {number} */ Preferences.prototype.getMaxFileSize = function () { return this._maxFileSize; }; module.exports = Preferences; }); ================================================ FILE: src/JSUtils/ScopeManager.js ================================================ /* * Copyright (c) 2013 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /* * Throughout this file, the term "outer scope" is used to refer to the outer- * most/global/root Scope objects for particular file. The term "inner scope" * is used to refer to a Scope object that is reachable via the child relation * from an outer scope. */ define(function (require, exports, module) { "use strict"; var _ = require("thirdparty/lodash"); var CodeMirror = require("thirdparty/CodeMirror/lib/codemirror"), DefaultDialogs = require("widgets/DefaultDialogs"), Dialogs = require("widgets/Dialogs"), DocumentManager = require("document/DocumentManager"), EditorManager = require("editor/EditorManager"), ExtensionUtils = require("utils/ExtensionUtils"), FileSystem = require("filesystem/FileSystem"), FileUtils = require("file/FileUtils"), LanguageManager = require("language/LanguageManager"), PreferencesManager = require("preferences/PreferencesManager"), ProjectManager = require("project/ProjectManager"), Strings = require("strings"), StringUtils = require("utils/StringUtils"), NodeDomain = require("utils/NodeDomain"), InMemoryFile = require("document/InMemoryFile"); var HintUtils = require("./HintUtils"), MessageIds = require("./MessageIds"), Preferences = require("./Preferences"); var ternEnvironment = [], pendingTernRequests = {}, builtinFiles = ["ecmascript.json", "browser.json", "jquery.json"], builtinLibraryNames = [], isDocumentDirty = false, _hintCount = 0, currentModule = null, documentChanges = null, // bounds of document changes preferences = null, deferredPreferences = null; var _bracketsPath = FileUtils.getNativeBracketsDirectoryPath(), _modulePath = FileUtils.getNativeModuleDirectoryPath(module), _nodePath = "node/TernNodeDomain", _absoluteModulePath = [_bracketsPath, _modulePath].join("/"), _domainPath = [_bracketsPath, _modulePath, _nodePath].join("/"); var MAX_HINTS = 30, // how often to reset the tern server LARGE_LINE_CHANGE = 100, LARGE_LINE_COUNT = 10000, OFFSET_ZERO = {line: 0, ch: 0}; var config = {}; /** * An array of library names that contain JavaScript builtins definitions. * * @return {Array.} - array of library names. */ function getBuiltins() { return builtinLibraryNames; } /** * Read in the json files that have type information for the builtins, dom,etc */ function initTernEnv() { var path = [_absoluteModulePath, "node/node_modules/tern/defs/"].join("/"), files = builtinFiles, library; files.forEach(function (i) { FileSystem.resolve(path + i, function (err, file) { if (!err) { FileUtils.readAsText(file).done(function (text) { library = JSON.parse(text); builtinLibraryNames.push(library["!name"]); ternEnvironment.push(library); }).fail(function (error) { console.log("failed to read tern config file " + i); }); } else { console.log("failed to read tern config file " + i); } }); }); } initTernEnv(); /** * Init preferences from a file in the project root or builtin * defaults if no file is found; * * @param {string=} projectRootPath - new project root path. Only needed * for unit tests. */ function initPreferences(projectRootPath) { // Reject the old preferences if they have not completed. if (deferredPreferences && deferredPreferences.state() === "pending") { deferredPreferences.reject(); } deferredPreferences = $.Deferred(); var pr = ProjectManager.getProjectRoot(); // Open preferences relative to the project root // Normally there is a project root, but for unit tests we need to // pass in a project root. if (pr) { projectRootPath = pr.fullPath; } else if (!projectRootPath) { console.log("initPreferences: projectRootPath has no value"); } var path = projectRootPath + Preferences.FILE_NAME; FileSystem.resolve(path, function (err, file) { if (!err) { FileUtils.readAsText(file).done(function (text) { var configObj = null; try { configObj = JSON.parse(text); } catch (e) { // continue with null configObj which will result in // default settings. console.log("Error parsing preference file: " + path); if (e instanceof SyntaxError) { console.log(e.message); } } preferences = new Preferences(configObj); deferredPreferences.resolve(); }).fail(function (error) { preferences = new Preferences(); deferredPreferences.resolve(); }); } else { preferences = new Preferences(); deferredPreferences.resolve(); } }); } /** * Will initialize preferences only if they do not exist. * */ function ensurePreferences() { if (!deferredPreferences) { initPreferences(); } } /** * Send a message to the tern module - if the module is being initialized, * the message will not be posted until initialization is complete */ function postMessage(msg) { if (currentModule) { currentModule.postMessage(msg); } } /** * Test if the directory should be excluded from analysis. * * @param {!string} path - full directory path. * @return {boolean} true if excluded, false otherwise. */ function isDirectoryExcluded(path) { var excludes = preferences.getExcludedDirectories(); if (!excludes) { return false; } var testPath = ProjectManager.makeProjectRelativeIfPossible(path); testPath = FileUtils.stripTrailingSlash(testPath); return excludes.test(testPath); } /** * Test if the file path is in current editor * * @param {string} filePath file path to test for exclusion. * @return {boolean} true if in editor, false otherwise. */ function isFileBeingEdited(filePath) { var currentEditor = EditorManager.getActiveEditor(), currentDoc = currentEditor && currentEditor.document; return (currentDoc && currentDoc.file.fullPath === filePath); } /** * Test if the file path is an internal exclusion. * * @param {string} path file path to test for exclusion. * @return {boolean} true if excluded, false otherwise. */ function isFileExcludedInternal(path) { // The detectedExclusions are files detected to be troublesome with current versions of Tern. // detectedExclusions is an array of full paths. var detectedExclusions = PreferencesManager.get("jscodehints.detectedExclusions") || []; if (detectedExclusions && detectedExclusions.indexOf(path) !== -1) { return true; } return false; } /** * Test if the file should be excluded from analysis. * * @param {!File} file - file to test for exclusion. * @return {boolean} true if excluded, false otherwise. */ function isFileExcluded(file) { if (file.name[0] === ".") { return true; } var languageID = LanguageManager.getLanguageForPath(file.fullPath).getId(); if (languageID !== HintUtils.LANGUAGE_ID) { return true; } var excludes = preferences.getExcludedFiles(); if (excludes && excludes.test(file.name)) { return true; } if (isFileExcludedInternal(file.fullPath)) { return true; } return false; } /** * Add a pending request waiting for the tern-module to complete. * If file is a detected exclusion, then reject request. * * @param {string} file - the name of the file * @param {{line: number, ch: number}} offset - the offset into the file the request is for * @param {string} type - the type of request * @return {jQuery.Promise} - the promise for the request */ function addPendingRequest(file, offset, type) { var requests, key = file + "@" + offset.line + "@" + offset.ch, $deferredRequest; // Reject detected exclusions if (isFileExcludedInternal(file)) { return (new $.Deferred()).reject().promise(); } if (_.has(pendingTernRequests, key)) { requests = pendingTernRequests[key]; } else { requests = {}; pendingTernRequests[key] = requests; } if (_.has(requests, type)) { $deferredRequest = requests[type]; } else { requests[type] = $deferredRequest = new $.Deferred(); } return $deferredRequest.promise(); } /** * Get any pending $.Deferred object waiting on the specified file and request type * @param {string} file - the file * @param {{line: number, ch: number}} offset - the offset into the file the request is for * @param {string} type - the type of request * @return {jQuery.Deferred} - the $.Deferred for the request */ function getPendingRequest(file, offset, type) { var key = file + "@" + offset.line + "@" + offset.ch; if (_.has(pendingTernRequests, key)) { var requests = pendingTernRequests[key], requestType = requests[type]; delete pendingTernRequests[key][type]; if (!Object.keys(requests).length) { delete pendingTernRequests[key]; } return requestType; } } /** * @param {string} file a relative path * @return {string} returns the path we resolved when we tried to parse the file, or undefined */ function getResolvedPath(file) { return currentModule.getResolvedPath(file); } /** * Get a Promise for the definition from TernJS, for the file & offset passed in. * @param {{type: string, name: string, offsetLines: number, text: string}} fileInfo * - type of update, name of file, and the text of the update. * For "full" updates, the whole text of the file is present. For "part" updates, * the changed portion of the text. For "empty" updates, the file has not been modified * and the text is empty. * @param {{line: number, ch: number}} offset - the offset in the file the hints should be calculate at * @return {jQuery.Promise} - a promise that will resolve to definition when * it is done */ function getJumptoDef(fileInfo, offset) { postMessage({ type: MessageIds.TERN_JUMPTODEF_MSG, fileInfo: fileInfo, offset: offset }); return addPendingRequest(fileInfo.name, offset, MessageIds.TERN_JUMPTODEF_MSG); } /** * check to see if the text we are sending to Tern is too long. * @param {string} the text to check * @return {string} the text, or the empty text if the original was too long */ function filterText(text) { var newText = text; if (text.length > preferences.getMaxFileSize()) { newText = ""; } return newText; } /** * Get the text of a document, applying any size restrictions * if necessary * @param {Document} document - the document to get the text from * @return {string} the text, or the empty text if the original was too long */ function getTextFromDocument(document) { var text = document.getText(); text = filterText(text); return text; } /** * Handle the response from the tern node domain when * it responds with the references * * @param response - the response from the node domain */ function handleRename(response) { if (response.error) { EditorManager.getActiveEditor().displayErrorMessageAtCursor(response.error); return; } var file = response.file, offset = response.offset; var $deferredFindRefs = getPendingRequest(file, offset, MessageIds.TERN_REFS); if ($deferredFindRefs) { $deferredFindRefs.resolveWith(null, [response]); } } /** * Request Jump-To-Definition from Tern. * * @param {session} session - the session * @param {Document} document - the document * @param {{line: number, ch: number}} offset - the offset into the document * @return {jQuery.Promise} - The promise will not complete until tern * has completed. */ function requestJumptoDef(session, document, offset) { var path = document.file.fullPath, fileInfo = { type: MessageIds.TERN_FILE_INFO_TYPE_FULL, name: path, offsetLines: 0, text: filterText(session.getJavascriptText()) }; var ternPromise = getJumptoDef(fileInfo, offset); return {promise: ternPromise}; } /** * Handle the response from the tern node domain when * it responds with the definition * * @param response - the response from the node domain */ function handleJumptoDef(response) { var file = response.file, offset = response.offset; var $deferredJump = getPendingRequest(file, offset, MessageIds.TERN_JUMPTODEF_MSG); if ($deferredJump) { response.fullPath = getResolvedPath(response.resultFile); $deferredJump.resolveWith(null, [response]); } } /** * Handle the response from the tern node domain when * it responds with the scope data * * @param response - the response from the node domain */ function handleScopeData(response) { var file = response.file, offset = response.offset; var $deferredJump = getPendingRequest(file, offset, MessageIds.TERN_SCOPEDATA_MSG); if ($deferredJump) { $deferredJump.resolveWith(null, [response]); } } /** * Get a Promise for the completions from TernJS, for the file & offset passed in. * * @param {{type: string, name: string, offsetLines: number, text: string}} fileInfo * - type of update, name of file, and the text of the update. * For "full" updates, the whole text of the file is present. For "part" updates, * the changed portion of the text. For "empty" updates, the file has not been modified * and the text is empty. * @param {{line: number, ch: number}} offset - the offset in the file the hints should be calculate at * @param {boolean} isProperty - true if getting a property hint, * otherwise getting an identifier hint. * @return {jQuery.Promise} - a promise that will resolve to an array of completions when * it is done */ function getTernHints(fileInfo, offset, isProperty) { /** * If the document is large and we have modified a small portions of it that * we are asking hints for, then send a partial document. */ postMessage({ type: MessageIds.TERN_COMPLETIONS_MSG, fileInfo: fileInfo, offset: offset, isProperty: isProperty }); return addPendingRequest(fileInfo.name, offset, MessageIds.TERN_COMPLETIONS_MSG); } /** * Get a Promise for the function type from TernJS. * @param {{type: string, name: string, offsetLines: number, text: string}} fileInfo * - type of update, name of file, and the text of the update. * For "full" updates, the whole text of the file is present. For "part" updates, * the changed portion of the text. For "empty" updates, the file has not been modified * and the text is empty. * @param {{line:number, ch:number}} offset - the line, column info for what we want the function type of. * @return {jQuery.Promise} - a promise that will resolve to the function type of the function being called. */ function getTernFunctionType(fileInfo, offset) { postMessage({ type: MessageIds.TERN_CALLED_FUNC_TYPE_MSG, fileInfo: fileInfo, offset: offset }); return addPendingRequest(fileInfo.name, offset, MessageIds.TERN_CALLED_FUNC_TYPE_MSG); } /** * Given a starting and ending position, get a code fragment that is self contained * enough to be compiled. * * @param {!Session} session - the current session * @param {{line: number, ch: number}} start - the starting position of the changes * @return {{type: string, name: string, offsetLines: number, text: string}} */ function getFragmentAround(session, start) { var minIndent = null, minLine = null, endLine, cm = session.editor._codeMirror, tabSize = cm.getOption("tabSize"), document = session.editor.document, p, min, indent, line; // expand range backwards for (p = start.line - 1, min = Math.max(0, p - 100); p >= min; --p) { line = session.getLine(p); var fn = line.search(/\bfunction\b/); if (fn >= 0) { indent = CodeMirror.countColumn(line, null, tabSize); if (minIndent === null || minIndent > indent) { if (session.getToken({line: p, ch: fn + 1}).type === "keyword") { minIndent = indent; minLine = p; } } } } if (minIndent === null) { minIndent = 0; } if (minLine === null) { minLine = min; } var max = Math.min(cm.lastLine(), start.line + 100), endCh = 0; for (endLine = start.line + 1; endLine < max; ++endLine) { line = cm.getLine(endLine); if (line.length > 0) { indent = CodeMirror.countColumn(line, null, tabSize); if (indent <= minIndent) { endCh = line.length; break; } } } var from = {line: minLine, ch: 0}, to = {line: endLine, ch: endCh}; return {type: MessageIds.TERN_FILE_INFO_TYPE_PART, name: document.file.fullPath, offsetLines: from.line, text: document.getRange(from, to)}; } /** * Get an object that describes what tern needs to know about the updated * file to produce a hint. As a side-effect of this calls the document * changes are reset. * * @param {!Session} session - the current session * @param {boolean=} preventPartialUpdates - if true, disallow partial updates. * Optional, defaults to false. * @return {{type: string, name: string, offsetLines: number, text: string}} */ function getFileInfo(session, preventPartialUpdates) { var start = session.getCursor(), end = start, document = session.editor.document, path = document.file.fullPath, isHtmlFile = LanguageManager.getLanguageForPath(path).getId() === "html", result; if (isHtmlFile) { result = {type: MessageIds.TERN_FILE_INFO_TYPE_FULL, name: path, text: session.getJavascriptText()}; } else if (!documentChanges) { result = {type: MessageIds.TERN_FILE_INFO_TYPE_EMPTY, name: path, text: ""}; } else if (!preventPartialUpdates && session.editor.lineCount() > LARGE_LINE_COUNT && (documentChanges.to - documentChanges.from < LARGE_LINE_CHANGE) && documentChanges.from <= start.line && documentChanges.to > end.line) { result = getFragmentAround(session, start); } else { result = {type: MessageIds.TERN_FILE_INFO_TYPE_FULL, name: path, text: getTextFromDocument(document)}; } documentChanges = null; return result; } /** * Get the current offset. The offset is adjusted for "part" updates. * * @param {!Session} session - the current session * @param {{type: string, name: string, offsetLines: number, text: string}} fileInfo * - type of update, name of file, and the text of the update. * For "full" updates, the whole text of the file is present. For "part" updates, * the changed portion of the text. For "empty" updates, the file has not been modified * and the text is empty. * @param {{line: number, ch: number}=} offset - the default offset (optional). Will * use the cursor if not provided. * @return {{line: number, ch: number}} */ function getOffset(session, fileInfo, offset) { var newOffset; if (offset) { newOffset = {line: offset.line, ch: offset.ch}; } else { newOffset = session.getCursor(); } if (fileInfo.type === MessageIds.TERN_FILE_INFO_TYPE_PART) { newOffset.line = Math.max(0, newOffset.line - fileInfo.offsetLines); } return newOffset; } /** * Get a Promise for all of the known properties from TernJS, for the directory and file. * The properties will be used as guesses in tern. * @param {Session} session - the active hinting session * @param {Document} document - the document for which scope info is * desired * @return {jQuery.Promise} - The promise will not complete until the tern * request has completed. */ function requestGuesses(session, document) { var $deferred = $.Deferred(), fileInfo = getFileInfo(session), offset = getOffset(session, fileInfo); postMessage({ type: MessageIds.TERN_GET_GUESSES_MSG, fileInfo: fileInfo, offset: offset }); var promise = addPendingRequest(fileInfo.name, offset, MessageIds.TERN_GET_GUESSES_MSG); promise.done(function (guesses) { session.setGuesses(guesses); $deferred.resolve(); }).fail(function () { $deferred.reject(); }); return $deferred.promise(); } /** * Handle the response from the tern node domain when * it responds with the list of completions * * @param {{file: string, offset: {line: number, ch: number}, completions:Array., * properties:Array.}} response - the response from node domain */ function handleTernCompletions(response) { var file = response.file, offset = response.offset, completions = response.completions, properties = response.properties, fnType = response.fnType, type = response.type, error = response.error, $deferredHints = getPendingRequest(file, offset, type); if ($deferredHints) { if (error) { $deferredHints.reject(); } else if (completions) { $deferredHints.resolveWith(null, [{completions: completions}]); } else if (properties) { $deferredHints.resolveWith(null, [{properties: properties}]); } else if (fnType) { $deferredHints.resolveWith(null, [fnType]); } } } /** * Handle the response from the tern node domain when * it responds to the get guesses message. * * @param {{file: string, type: string, offset: {line: number, ch: number}, * properties: Array.}} response - * the response from node domain contains the guesses for a * property lookup. */ function handleGetGuesses(response) { var path = response.file, type = response.type, offset = response.offset, $deferredHints = getPendingRequest(path, offset, type); if ($deferredHints) { $deferredHints.resolveWith(null, [response.properties]); } } /** * Handle the response from the tern node domain when * it responds to the update file message. * * @param {{path: string, type: string}} response - the response from node domain */ function handleUpdateFile(response) { var path = response.path, type = response.type, $deferredHints = getPendingRequest(path, OFFSET_ZERO, type); if ($deferredHints) { $deferredHints.resolve(); } } /** * Handle timed out inference * * @param {{path: string, type: string}} response - the response from node domain */ function handleTimedOut(response) { var detectedExclusions = PreferencesManager.get("jscodehints.detectedExclusions") || [], filePath = response.file; // Don't exclude the file currently being edited if (isFileBeingEdited(filePath)) { return; } // Handle file that is already excluded if (detectedExclusions.indexOf(filePath) !== -1) { console.log("JavaScriptCodeHints.handleTimedOut: file already in detectedExclusions array timed out: " + filePath); return; } // Save detected exclusion in project prefs so no further time is wasted on it detectedExclusions.push(filePath); PreferencesManager.set("jscodehints.detectedExclusions", detectedExclusions, { location: { scope: "project" } }); // Show informational dialog Dialogs.showModalDialog( DefaultDialogs.DIALOG_ID_INFO, Strings.DETECTED_EXCLUSION_TITLE, StringUtils.format( Strings.DETECTED_EXCLUSION_INFO, StringUtils.breakableUrl(filePath) ), [ { className : Dialogs.DIALOG_BTN_CLASS_PRIMARY, id : Dialogs.DIALOG_BTN_OK, text : Strings.OK } ] ); } DocumentManager.on("dirtyFlagChange", function (event, changedDoc) { if (changedDoc.file.fullPath) { postMessage({ type: MessageIds.TERN_UPDATE_DIRTY_FILE, name: changedDoc.file.fullPath, action: changedDoc.isDirty }); } }); // Clear dirty document list in tern node domain ProjectManager.on("beforeProjectClose", function () { postMessage({ type: MessageIds.TERN_CLEAR_DIRTY_FILES_LIST }); }); /** * Encapsulate all the logic to talk to the tern module. This will create * a new instance of a TernModule, which the rest of the hinting code can use to talk * to the tern node domain, without worrying about initialization, priming the pump, etc. * */ function TernModule() { var ternPromise = null, addFilesPromise = null, rootTernDir = null, projectRoot = null, stopAddingFiles = false, resolvedFiles = {}, // file -> resolved file numInitialFiles = 0, numResolvedFiles = 0, numAddedFiles = 0, _ternNodeDomain = null; /** * @param {string} file a relative path * @return {string} returns the path we resolved when we tried to parse the file, or undefined */ function getResolvedPath(file) { return resolvedFiles[file]; } /** * Determine whether the current set of files are using modules to pull in * additional files. * * @return {boolean} - true if more files than the current directory have * been read in. */ function usingModules() { return numInitialFiles !== numResolvedFiles; } /** * Send a message to the tern node domain - if the module is being initialized, * the message will not be posted until initialization is complete */ function postMessage(msg) { addFilesPromise.done(function (ternModule) { // If an error came up during file handling, bail out now if (!_ternNodeDomain) { return; } if (config.debug) { console.debug("Sending message", msg); } _ternNodeDomain.exec("invokeTernCommand", msg); }); } /** * Send a message to the tern node domain - this is only for messages that * need to be sent before and while the addFilesPromise is being resolved. */ function _postMessageByPass(msg) { ternPromise.done(function (ternModule) { if (config.debug) { console.debug("Sending message", msg); } _ternNodeDomain.exec("invokeTernCommand", msg); }); } /** * Update tern with the new contents of a given file. * * @param {Document} document - the document to update * @return {jQuery.Promise} - the promise for the request */ function updateTernFile(document) { var path = document.file.fullPath; _postMessageByPass({ type : MessageIds.TERN_UPDATE_FILE_MSG, path : path, text : getTextFromDocument(document) }); return addPendingRequest(path, OFFSET_ZERO, MessageIds.TERN_UPDATE_FILE_MSG); } /** * Handle a request from the tern node domain for text of a file * * @param {{file:string}} request - the request from the tern node domain. Should be an Object containing the name * of the file tern wants the contents of */ function handleTernGetFile(request) { function replyWith(name, txt) { _postMessageByPass({ type: MessageIds.TERN_GET_FILE_MSG, file: name, text: txt }); } var name = request.file; /** * Helper function to get the text of a given document and send it to tern. * If DocumentManager successfully gets the file's text then we'll send it to the tern node domain. * The Promise for getDocumentText() is returned so that custom fail functions can be used. * * @param {string} filePath - the path of the file to get the text of * @return {jQuery.Promise} - the Promise returned from DocumentMangaer.getDocumentText() */ function getDocText(filePath) { if (!FileSystem.isAbsolutePath(filePath) || // don't handle URLs filePath.slice(0, 2) === "//") { // don't handle protocol-relative URLs like //example.com/main.js (see #10566) return (new $.Deferred()).reject().promise(); } var file = FileSystem.getFileForPath(filePath), promise = DocumentManager.getDocumentText(file); promise.done(function (docText) { resolvedFiles[name] = filePath; numResolvedFiles++; replyWith(name, filterText(docText)); }); return promise; } /** * Helper function to find any files in the project that end with the * name we are looking for. This is so we can find requirejs modules * when the baseUrl is unknown, or when the project root is not the same * as the script root (e.g. if you open the 'brackets' dir instead of 'brackets/src' dir). */ function findNameInProject() { // check for any files in project that end with the right path. var fileName = name.substring(name.lastIndexOf("/") + 1); function _fileFilter(entry) { return entry.name === fileName; } ProjectManager.getAllFiles(_fileFilter).done(function (files) { var file; files = files.filter(function (file) { var pos = file.fullPath.length - name.length; return pos === file.fullPath.lastIndexOf(name); }); if (files.length === 1) { file = files[0]; } if (file) { getDocText(file.fullPath).fail(function () { replyWith(name, ""); }); } else { replyWith(name, ""); } }); } if (!isFileExcludedInternal(name)) { getDocText(name).fail(function () { getDocText(rootTernDir + name).fail(function () { // check relative to project root getDocText(projectRoot + name) // last look for any files that end with the right path // in the project .fail(findNameInProject); }); }); } } /** * Prime the pump for a fast first lookup. * * @param {string} path - full path of file * @return {jQuery.Promise} - the promise for the request */ function primePump(path, isUntitledDoc) { _postMessageByPass({ type : MessageIds.TERN_PRIME_PUMP_MSG, path : path, isUntitledDoc : isUntitledDoc }); return addPendingRequest(path, OFFSET_ZERO, MessageIds.TERN_PRIME_PUMP_MSG); } /** * Handle the response from the tern node domain when * it responds to the prime pump message. * * @param {{path: string, type: string}} response - the response from node domain */ function handlePrimePumpCompletion(response) { var path = response.path, type = response.type, $deferredHints = getPendingRequest(path, OFFSET_ZERO, type); if ($deferredHints) { $deferredHints.resolve(); } } /** * Add new files to tern, keeping any previous files. * The tern server must be initialized before making * this call. * * @param {Array.} files - array of file to add to tern. * @return {boolean} - true if more files may be added, false if maximum has been reached. */ function addFilesToTern(files) { // limit the number of files added to tern. var maxFileCount = preferences.getMaxFileCount(); if (numResolvedFiles + numAddedFiles < maxFileCount) { var available = maxFileCount - numResolvedFiles - numAddedFiles; if (available < files.length) { files = files.slice(0, available); } numAddedFiles += files.length; ternPromise.done(function (ternModule) { var msg = { type : MessageIds.TERN_ADD_FILES_MSG, files : files }; if (config.debug) { console.debug("Sending message", msg); } _ternNodeDomain.exec("invokeTernCommand", msg); }); } else { stopAddingFiles = true; } return stopAddingFiles; } /** * Add the files in the directory and subdirectories of a given directory * to tern. * * @param {string} dir - the root directory to add. * @param {function ()} doneCallback - called when all files have been * added to tern. */ function addAllFilesAndSubdirectories(dir, doneCallback) { FileSystem.resolve(dir, function (err, directory) { function visitor(entry) { if (entry.isFile) { if (!isFileExcluded(entry)) { // ignore .dotfiles and non-.js files addFilesToTern([entry.fullPath]); } } else { return !isDirectoryExcluded(entry.fullPath) && entry.name.indexOf(".") !== 0 && !stopAddingFiles; } } if (err) { return; } if (dir === FileSystem.getDirectoryForPath(rootTernDir)) { doneCallback(); return; } directory.visit(visitor, doneCallback); }); } /** * Init the Tern module that does all the code hinting work. */ function initTernModule() { var moduleDeferred = $.Deferred(); ternPromise = moduleDeferred.promise(); function prepareTern() { _ternNodeDomain.exec("setInterface", { messageIds : MessageIds }); _ternNodeDomain.exec("invokeTernCommand", { type: MessageIds.SET_CONFIG, config: config }); moduleDeferred.resolveWith(null, [_ternNodeDomain]); } if (_ternNodeDomain) { _ternNodeDomain.exec("resetTernServer"); moduleDeferred.resolveWith(null, [_ternNodeDomain]); } else { _ternNodeDomain = new NodeDomain("TernNodeDomain", _domainPath); _ternNodeDomain.on("data", function (evt, data) { if (config.debug) { console.log("Message received", data.type); } var response = data, type = response.type; if (type === MessageIds.TERN_COMPLETIONS_MSG || type === MessageIds.TERN_CALLED_FUNC_TYPE_MSG) { // handle any completions the tern server calculated handleTernCompletions(response); } else if (type === MessageIds.TERN_GET_FILE_MSG) { // handle a request for the contents of a file handleTernGetFile(response); } else if (type === MessageIds.TERN_JUMPTODEF_MSG) { handleJumptoDef(response); } else if (type === MessageIds.TERN_SCOPEDATA_MSG) { handleScopeData(response); } else if (type === MessageIds.TERN_REFS) { handleRename(response); } else if (type === MessageIds.TERN_PRIME_PUMP_MSG) { handlePrimePumpCompletion(response); } else if (type === MessageIds.TERN_GET_GUESSES_MSG) { handleGetGuesses(response); } else if (type === MessageIds.TERN_UPDATE_FILE_MSG) { handleUpdateFile(response); } else if (type === MessageIds.TERN_INFERENCE_TIMEDOUT) { handleTimedOut(response); } else if (type === MessageIds.TERN_WORKER_READY) { moduleDeferred.resolveWith(null, [_ternNodeDomain]); } else if (type === "RE_INIT_TERN") { // Ensure the request is because of a node restart if (currentModule) { prepareTern(); // Mark the module with resetForced, then creation of TernModule will // happen again as part of '_maybeReset' call currentModule.resetForced = true; } } else { console.log("Tern Module: " + (response.log || response)); } }); _ternNodeDomain.promise().done(prepareTern); } } /** * Create a new tern server. */ function initTernServer(dir, files) { initTernModule(); numResolvedFiles = 0; numAddedFiles = 0; stopAddingFiles = false; numInitialFiles = files.length; ternPromise.done(function (ternModule) { var msg = { type : MessageIds.TERN_INIT_MSG, dir : dir, files : files, env : ternEnvironment, timeout : PreferencesManager.get("jscodehints.inferenceTimeout") }; _ternNodeDomain.exec("invokeTernCommand", msg); }); rootTernDir = dir + "/"; } /** * We can skip tern initialization if we are opening a file that has * already been added to tern. * * @param {string} newFile - full path of new file being opened in the editor. * @return {boolean} - true if tern initialization should be skipped, * false otherwise. */ function canSkipTernInitialization(newFile) { return resolvedFiles[newFile] !== undefined; } /** * Do the work to initialize a code hinting session. * * @param {Session} session - the active hinting session (TODO: currently unused) * @param {!Document} document - the document the editor has changed to * @param {?Document} previousDocument - the document the editor has changed from */ function doEditorChange(session, document, previousDocument) { var file = document.file, path = file.fullPath, dir = file.parentPath, pr; var addFilesDeferred = $.Deferred(); documentChanges = null; addFilesPromise = addFilesDeferred.promise(); pr = ProjectManager.getProjectRoot() ? ProjectManager.getProjectRoot().fullPath : null; // avoid re-initializing tern if possible. if (canSkipTernInitialization(path)) { // update the previous document in tern to prevent stale files. if (isDocumentDirty && previousDocument) { var updateFilePromise = updateTernFile(previousDocument); updateFilePromise.done(function () { primePump(path, document.isUntitled()); addFilesDeferred.resolveWith(null, [_ternNodeDomain]); }); } else { addFilesDeferred.resolveWith(null, [_ternNodeDomain]); } isDocumentDirty = false; return; } if (previousDocument && previousDocument.isDirty) { updateTernFile(previousDocument); } isDocumentDirty = false; resolvedFiles = {}; projectRoot = pr; ensurePreferences(); deferredPreferences.done(function () { if (file instanceof InMemoryFile) { initTernServer(pr, []); var hintsPromise = primePump(path, true); hintsPromise.done(function () { addFilesDeferred.resolveWith(null, [_ternNodeDomain]); }); return; } FileSystem.resolve(dir, function (err, directory) { if (err) { console.error("Error resolving", dir); addFilesDeferred.resolveWith(null); return; } directory.getContents(function (err, contents) { if (err) { console.error("Error getting contents for", directory); addFilesDeferred.resolveWith(null); return; } var files = contents .filter(function (entry) { return entry.isFile && !isFileExcluded(entry); }) .map(function (entry) { return entry.fullPath; }); initTernServer(dir, files); var hintsPromise = primePump(path, false); hintsPromise.done(function () { if (!usingModules()) { // Read the subdirectories of the new file's directory. // Read them first in case there are too many files to // read in the project. addAllFilesAndSubdirectories(dir, function () { // If the file is in the project root, then read // all the files under the project root. var currentDir = (dir + "/"); if (projectRoot && currentDir !== projectRoot && currentDir.indexOf(projectRoot) === 0) { addAllFilesAndSubdirectories(projectRoot, function () { // prime the pump again but this time don't wait // for completion. primePump(path, false); addFilesDeferred.resolveWith(null, [_ternNodeDomain]); }); } else { addFilesDeferred.resolveWith(null, [_ternNodeDomain]); } }); } else { addFilesDeferred.resolveWith(null, [_ternNodeDomain]); } }); }); }); }); } /** * Called each time a new editor becomes active. * * @param {Session} session - the active hinting session (TODO: currently unused by doEditorChange()) * @param {!Document} document - the document of the editor that has changed * @param {?Document} previousDocument - the document of the editor is changing from */ function handleEditorChange(session, document, previousDocument) { if (addFilesPromise === null) { doEditorChange(session, document, previousDocument); } else { addFilesPromise.done(function () { doEditorChange(session, document, previousDocument); }); } } /** * Do some cleanup when a project is closed. * * We can clean up the node tern server we use to calculate hints now, since * we know we will need to re-init it in any new project that is opened. */ function resetModule() { function resetTernServer() { if (_ternNodeDomain.ready()) { _ternNodeDomain.exec('resetTernServer'); } } if (_ternNodeDomain) { if (addFilesPromise) { // If we're in the middle of added files, don't reset // until we're done addFilesPromise.done(resetTernServer).fail(resetTernServer); } else { resetTernServer(); } } } function whenReady(func) { addFilesPromise.done(func); } this.resetModule = resetModule; this.handleEditorChange = handleEditorChange; this.postMessage = postMessage; this.getResolvedPath = getResolvedPath; this.whenReady = whenReady; return this; } var resettingDeferred = null; /** * reset the tern module, if necessary. * * During debugging, you can turn this automatic resetting behavior off * by running this in the console: * brackets._configureJSCodeHints({ noReset: true }) * * This function is also used in unit testing with the "force" flag to * reset the module for each test to start with a clean environment. * * @param {Session} session * @param {Document} document * @param {boolean} force true to force a reset regardless of how long since the last one * @return {Promise} Promise resolved when the module is ready. * The new (or current, if there was no reset) module is passed to the callback. */ function _maybeReset(session, document, force) { var newTernModule; // if we're in the middle of a reset, don't have to check // the new module will be online soon if (!resettingDeferred) { // We don't reset if the debugging flag is set // because it's easier to debug if the module isn't // getting reset all the time. if (currentModule.resetForced || force || (!config.noReset && ++_hintCount > MAX_HINTS)) { if (config.debug) { console.debug("Resetting tern module"); } resettingDeferred = new $.Deferred(); newTernModule = new TernModule(); newTernModule.handleEditorChange(session, document, null); newTernModule.whenReady(function () { // reset the old module currentModule.resetModule(); currentModule = newTernModule; resettingDeferred.resolve(currentModule); // all done reseting resettingDeferred = null; }); _hintCount = 0; } else { var d = new $.Deferred(); d.resolve(currentModule); return d.promise(); } } return resettingDeferred.promise(); } /** * Request a parameter hint from Tern. * * @param {Session} session - the active hinting session * @param {{line: number, ch: number}} functionOffset - the offset of the function call. * @return {jQuery.Promise} - The promise will not complete until the * hint has completed. */ function requestParameterHint(session, functionOffset) { var $deferredHints = $.Deferred(), fileInfo = getFileInfo(session, true), offset = getOffset(session, fileInfo, functionOffset), fnTypePromise = getTernFunctionType(fileInfo, offset); $.when(fnTypePromise).done( function (fnType) { session.setFnType(fnType); session.setFunctionCallPos(functionOffset); $deferredHints.resolveWith(null, [fnType]); } ).fail(function () { $deferredHints.reject(); }); return $deferredHints.promise(); } /** * Request hints from Tern. * * Note that successive calls to getScope may return the same objects, so * clients that wish to modify those objects (e.g., by annotating them based * on some temporary context) should copy them first. See, e.g., * Session.getHints(). * * @param {Session} session - the active hinting session * @param {Document} document - the document for which scope info is * desired * @return {jQuery.Promise} - The promise will not complete until the tern * hints have completed. */ function requestHints(session, document) { var $deferredHints = $.Deferred(), hintPromise, sessionType = session.getType(), fileInfo = getFileInfo(session), offset = getOffset(session, fileInfo, null); _maybeReset(session, document); hintPromise = getTernHints(fileInfo, offset, sessionType.property); $.when(hintPromise).done( function (completions, fnType) { if (completions.completions) { session.setTernHints(completions.completions); session.setGuesses(null); } else { session.setTernHints([]); session.setGuesses(completions.properties); } $deferredHints.resolveWith(null); } ).fail(function () { $deferredHints.reject(); }); return $deferredHints.promise(); } /** * Track the update area of the current document so we can tell if we can send * partial updates to tern or not. * * @param {Array.<{from: {line:number, ch: number}, to: {line:number, ch: number}, * text: Array}>} changeList - the document changes from the current change event */ function trackChange(changeList) { var changed = documentChanges, i; if (changed === null) { documentChanges = changed = {from: changeList[0].from.line, to: changeList[0].from.line}; if (config.debug) { console.debug("ScopeManager: document has changed"); } } for (i = 0; i < changeList.length; i++) { var thisChange = changeList[i], end = thisChange.from.line + (thisChange.text.length - 1); if (thisChange.from.line < changed.to) { changed.to = changed.to - (thisChange.to.line - end); } if (end >= changed.to) { changed.to = end + 1; } if (changed.from > thisChange.from.line) { changed.from = thisChange.from.line; } } } /* * Called each time the file associated with the active editor changes. * Marks the file as being dirty. * * @param {from: {line:number, ch: number}, to: {line:number, ch: number}} */ function handleFileChange(changeList) { isDocumentDirty = true; trackChange(changeList); } /** * Called each time a new editor becomes active. * * @param {Session} session - the active hinting session * @param {Document} document - the document of the editor that has changed * @param {?Document} previousDocument - the document of the editor is changing from */ function handleEditorChange(session, document, previousDocument) { if (!currentModule) { currentModule = new TernModule(); } return currentModule.handleEditorChange(session, document, previousDocument); } /** * Do some cleanup when a project is closed. * Clean up previous analysis data from the module */ function handleProjectClose() { if (currentModule) { currentModule.resetModule(); } } /** * Read in project preferences when a new project is opened. * Look in the project root directory for a preference file. * * @param {string=} projectRootPath - new project root path(optional). * Only needed for unit tests. */ function handleProjectOpen(projectRootPath) { initPreferences(projectRootPath); } /** Used to avoid timing bugs in unit tests */ function _readyPromise() { return deferredPreferences; } /** * @private * * Update the configuration in the tern node domain. */ function _setConfig(configUpdate) { config = brackets._configureJSCodeHints.config; postMessage({ type: MessageIds.SET_CONFIG, config: configUpdate }); } exports._setConfig = _setConfig; exports._maybeReset = _maybeReset; exports.getBuiltins = getBuiltins; exports.getResolvedPath = getResolvedPath; exports.getTernHints = getTernHints; exports.handleEditorChange = handleEditorChange; exports.requestGuesses = requestGuesses; exports.handleFileChange = handleFileChange; exports.requestHints = requestHints; exports.requestJumptoDef = requestJumptoDef; exports.requestParameterHint = requestParameterHint; exports.handleProjectClose = handleProjectClose; exports.handleProjectOpen = handleProjectOpen; exports._readyPromise = _readyPromise; exports.filterText = filterText; exports.postMessage = postMessage; exports.addPendingRequest = addPendingRequest; }); ================================================ FILE: src/JSUtils/Session.js ================================================ /* * Copyright (c) 2013 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /*jslint regexp: true */ define(function (require, exports, module) { "use strict"; var StringMatch = require("utils/StringMatch"), TokenUtils = require("utils/TokenUtils"), LanguageManager = require("language/LanguageManager"), HTMLUtils = require("language/HTMLUtils"), HintUtils = require("JSUtils/HintUtils"), ScopeManager = require("JSUtils/ScopeManager"), Acorn = require("node_modules/acorn/dist/acorn"), Acorn_Loose = require("node_modules/acorn/dist/acorn_loose"); /** * Session objects encapsulate state associated with a hinting session * and provide methods for updating and querying the session. * * @constructor * @param {Editor} editor - the editor context for the session */ function Session(editor) { this.editor = editor; this.path = editor.document.file.fullPath; this.ternHints = []; this.ternGuesses = null; this.fnType = null; this.builtins = null; } /** * Get the builtin libraries tern is using. * * @return {Array.} - array of library names. * @private */ Session.prototype._getBuiltins = function () { if (!this.builtins) { this.builtins = ScopeManager.getBuiltins(); this.builtins.push("requirejs.js"); // consider these globals as well. } return this.builtins; }; /** * Get the name of the file associated with the current session * * @return {string} - the full pathname of the file associated with the * current session */ Session.prototype.getPath = function () { return this.path; }; /** * Get the current cursor position. * * @return {{line: number, ch: number}} - the current cursor position */ Session.prototype.getCursor = function () { return this.editor.getCursorPos(); }; /** * Get the text of a line. * * @param {number} line - the line number * @return {string} - the text of the line */ Session.prototype.getLine = function (line) { var doc = this.editor.document; return doc.getLine(line); }; /** * Get the offset of the current cursor position * * @return {number} - the offset into the current document of the current * cursor */ Session.prototype.getOffset = function () { var cursor = this.getCursor(); return this.getOffsetFromCursor(cursor); }; /** * Get the offset of a cursor position * * @param {{line: number, ch: number}} the line/col info * @return {number} - the offset into the current document of the cursor */ Session.prototype.getOffsetFromCursor = function (cursor) { return this.editor.indexFromPos(cursor); }; /** * Get the token at the given cursor position, or at the current cursor * if none is given. * * @param {?{line: number, ch: number}} cursor - the cursor position * at which to retrieve a token * @return {Object} - the CodeMirror token at the given cursor position */ Session.prototype.getToken = function (cursor) { var cm = this.editor._codeMirror; if (cursor) { return TokenUtils.getTokenAt(cm, cursor); } else { return TokenUtils.getTokenAt(cm, this.getCursor()); } }; /** * Get the token after the one at the given cursor position * * @param {{line: number, ch: number}} cursor - cursor position before * which a token should be retrieved * @return {Object} - the CodeMirror token after the one at the given * cursor position */ Session.prototype.getNextTokenOnLine = function (cursor) { cursor = this.getNextCursorOnLine(cursor); if (cursor) { return this.getToken(cursor); } return null; }; /** * Get the next cursor position on the line, or null if there isn't one. * * @return {?{line: number, ch: number}} - the cursor position * immediately following the current cursor position, or null if * none exists. */ Session.prototype.getNextCursorOnLine = function (cursor) { var doc = this.editor.document, line = doc.getLine(cursor.line); if (cursor.ch < line.length) { return { ch : cursor.ch + 1, line: cursor.line }; } else { return null; } }; /** * Get the token before the one at the given cursor position * * @param {{line: number, ch: number}} cursor - cursor position after * which a token should be retrieved * @return {Object} - the CodeMirror token before the one at the given * cursor position */ Session.prototype._getPreviousToken = function (cursor) { var token = this.getToken(cursor), prev = token, doc = this.editor.document; do { if (prev.start < cursor.ch) { cursor.ch = prev.start; } else if (prev.start > 0) { cursor.ch = prev.start - 1; } else if (cursor.line > 0) { cursor.ch = doc.getLine(cursor.line - 1).length; cursor.line--; } else { break; } prev = this.getToken(cursor); } while (!/\S/.test(prev.string)); return prev; }; /** * Get the token after the one at the given cursor position * * @param {{line: number, ch: number}} cursor - cursor position after * which a token should be retrieved * @param {boolean} skipWhitespace - true if this should skip over whitespace tokens * @return {Object} - the CodeMirror token after the one at the given * cursor position */ Session.prototype.getNextToken = function (cursor, skipWhitespace) { var token = this.getToken(cursor), next = token, doc = this.editor.document; do { if (next.end > cursor.ch) { cursor.ch = next.end; } else if (next.end < doc.getLine(cursor.line).length) { cursor.ch = next.end + 1; } else if (doc.getLine(cursor.line + 1)) { cursor.ch = 0; cursor.line++; } else { next = null; break; } next = this.getToken(cursor); } while (skipWhitespace && !/\S/.test(next.string)); return next; }; /** * Calculate a query string relative to the current cursor position * and token. E.g., from a state "identier", the query string is * "identi". * * @return {string} - the query string for the current cursor position */ Session.prototype.getQuery = function () { var cursor = this.getCursor(), token = this.getToken(cursor), query = "", start = cursor.ch, end = start; if (token) { var line = this.getLine(cursor.line); while (start > 0) { if (HintUtils.maybeIdentifier(line[start - 1])) { start--; } else { break; } } query = line.substring(start, end); } return query; }; /** * Find the context of a property lookup. For example, for a lookup * foo(bar, baz(quux)).prop, foo is the context. * * @param {{line: number, ch: number}} cursor - the cursor position * at which context information is to be retrieved * @param {number=} depth - the current depth of the parenthesis stack, or * undefined if the depth is 0. * @return {string} - the context for the property that was looked up */ Session.prototype.getContext = function (cursor, depth) { var token = this.getToken(cursor); if (depth === undefined) { depth = 0; } if (token.string === ")") { this._getPreviousToken(cursor); return this.getContext(cursor, ++depth); } else if (token.string === "(") { this._getPreviousToken(cursor); return this.getContext(cursor, --depth); } else { if (depth > 0 || token.string === ".") { this._getPreviousToken(cursor); return this.getContext(cursor, depth); } else { return token.string; } } }; /** * @return {{line:number, ch:number}} - the line, col info for where the previous "." * in a property lookup occurred, or undefined if no previous "." was found. */ Session.prototype.findPreviousDot = function () { var cursor = this.getCursor(), token = this.getToken(cursor); // If the cursor is right after the dot, then the current token will be "." if (token && token.string === ".") { return cursor; } else { // If something has been typed like 'foo.b' then we have to look back 2 tokens // to get past the 'b' token token = this._getPreviousToken(cursor); if (token && token.string === ".") { return cursor; } } return undefined; }; /** * * @param {Object} token - a CodeMirror token * @return {*} - the lexical state of the token */ function getLexicalState(token) { if (token.state.lexical) { // in a javascript file this is just in the state field return token.state.lexical; } else if (token.state.localState && token.state.localState.lexical) { // inline javascript in an html file will have this in // the localState field return token.state.localState.lexical; } } /** * Determine if the caret is either within a function call or on the function call itself. * * @return {{inFunctionCall: boolean, functionCallPos: {line: number, ch: number}}} * inFunctionCall - true if the caret if either within a function call or on the * function call itself. * functionCallPos - the offset of the '(' character of the function call if inFunctionCall * is true, otherwise undefined. */ Session.prototype.getFunctionInfo = function () { var inFunctionCall = false, cursor = this.getCursor(), functionCallPos, token = this.getToken(cursor), lexical, self = this, foundCall = false; /** * Test if the cursor is on a function identifier * * @return {Object} - lexical state if on a function identifier, null otherwise. */ function isOnFunctionIdentifier() { // Check if we might be on function identifier of the function call. var type = token.type, nextToken, localLexical, localCursor = {line: cursor.line, ch: token.end}; if (type === "variable-2" || type === "variable" || type === "property") { nextToken = self.getNextToken(localCursor, true); if (nextToken && nextToken.string === "(") { localLexical = getLexicalState(nextToken); return localLexical; } } return null; } /** * Test is a lexical state is in a function call. * * @param {Object} lex - lexical state. * @return {Object | boolean} * */ function isInFunctionalCall(lex) { // in a call, or inside array or object brackets that are inside a function. return (lex && (lex.info === "call" || (lex.info === undefined && (lex.type === "]" || lex.type === "}") && lex.prev.info === "call"))); } if (token) { // if this token is part of a function call, then the tokens lexical info // will be annotated with "call". // If the cursor is inside an array, "[]", or object, "{}", the lexical state // will be undefined, not "call". lexical.prev will be the function state. // Handle this case and then set "lexical" to lexical.prev. // Also test if the cursor is on a function identifier of a function call. lexical = getLexicalState(token); foundCall = isInFunctionalCall(lexical); if (!foundCall) { lexical = isOnFunctionIdentifier(); foundCall = isInFunctionalCall(lexical); } if (foundCall) { // we need to find the location of the called function so that we can request the functions type. // the token's lexical info will contain the column where the open "(" for the // function call occurs, but for whatever reason it does not have the line, so // we have to walk back and try to find the correct location. We do this by walking // up the lines starting with the line the token is on, and seeing if any of the lines // have "(" at the column indicated by the tokens lexical state. // We walk back 9 lines, as that should be far enough to find most function calls, // and it will prevent us from walking back thousands of lines if something went wrong. // there is nothing magical about 9 lines, and it can be adjusted if it doesn't seem to be // working well if (lexical.info === undefined) { lexical = lexical.prev; } var col = lexical.info === "call" ? lexical.column : lexical.prev.column, line, e, found; for (line = this.getCursor().line, e = Math.max(0, line - 9), found = false; line >= e; --line) { if (this.getLine(line).charAt(col) === "(") { found = true; break; } } if (found) { inFunctionCall = true; functionCallPos = {line: line, ch: col}; } } } return { inFunctionCall: inFunctionCall, functionCallPos: functionCallPos }; }; /** * Get the type of the current session, i.e., whether it is a property * lookup and, if so, what the context of the lookup is. * * @return {{property: boolean, context: string} - an Object consisting * of a {boolean} "property" that indicates whether or not the type of * the session is a property lookup, and a {string} "context" that * indicates the object context (as described in getContext above) of * the property lookup, or null if there is none. The context is * always null for non-property lookups. */ Session.prototype.getType = function () { var propertyLookup = false, context = null, cursor = this.getCursor(), token = this.getToken(cursor); if (token) { if (token.type === "property") { propertyLookup = true; } cursor = this.findPreviousDot(); if (cursor) { propertyLookup = true; context = this.getContext(cursor); } } return { property: propertyLookup, context: context }; }; // Comparison function used for sorting that does a case-insensitive string // comparison on the "value" field of both objects. Unlike a normal string // comparison, however, this sorts leading "_" to the bottom, given that a // leading "_" usually denotes a private value. function penalizeUnderscoreValueCompare(a, b) { var aName = a.value.toLowerCase(), bName = b.value.toLowerCase(); // this sort function will cause _ to sort lower than lower case // alphabetical letters if (aName[0] === "_" && bName[0] !== "_") { return 1; } else if (bName[0] === "_" && aName[0] !== "_") { return -1; } if (aName < bName) { return -1; } else if (aName > bName) { return 1; } return 0; } /** * Get a list of hints for the current session using the current scope * information. * * @param {string} query - the query prefix * @param {StringMatcher} matcher - the class to find query matches and sort the results * @return {hints: Array., needGuesses: boolean} - array of * matching hints. If needGuesses is true, then the caller needs to * request guesses and call getHints again. */ Session.prototype.getHints = function (query, matcher) { if (query === undefined) { query = ""; } var MAX_DISPLAYED_HINTS = 500, type = this.getType(), builtins = this._getBuiltins(), needGuesses = false, hints; /** * Is the origin one of the builtin files. * * @param {string} origin */ function isBuiltin(origin) { return builtins.indexOf(origin) !== -1; } /** * Filter an array hints using a given query and matcher. * The hints are returned in the format of the matcher. * The matcher returns the value in the "label" property, * the match score in "matchGoodness" property. * * @param {Array} hints - array of hints * @param {StringMatcher} matcher * @return {Array} - array of matching hints. */ function filterWithQueryAndMatcher(hints, matcher) { var matchResults = $.map(hints, function (hint) { var searchResult = matcher.match(hint.value, query); if (searchResult) { searchResult.value = hint.value; searchResult.guess = hint.guess; searchResult.type = hint.type; if (hint.keyword !== undefined) { searchResult.keyword = hint.keyword; } if (hint.literal !== undefined) { searchResult.literal = hint.literal; } if (hint.depth !== undefined) { searchResult.depth = hint.depth; } if (hint.doc) { searchResult.doc = hint.doc; } if (hint.url) { searchResult.url = hint.url; } if (!type.property && !type.showFunctionType && hint.origin && isBuiltin(hint.origin)) { searchResult.builtin = 1; } else { searchResult.builtin = 0; } } return searchResult; }); return matchResults; } if (type.property) { hints = this.ternHints || []; hints = filterWithQueryAndMatcher(hints, matcher); // If there are no hints then switch over to guesses. if (hints.length === 0) { if (this.ternGuesses) { hints = filterWithQueryAndMatcher(this.ternGuesses, matcher); } else { needGuesses = true; } } StringMatch.multiFieldSort(hints, [ "matchGoodness", penalizeUnderscoreValueCompare ]); } else { // identifiers, literals, and keywords hints = this.ternHints || []; hints = hints.concat(HintUtils.LITERALS); hints = hints.concat(HintUtils.KEYWORDS); hints = filterWithQueryAndMatcher(hints, matcher); StringMatch.multiFieldSort(hints, [ "matchGoodness", "depth", "builtin", penalizeUnderscoreValueCompare ]); } if (hints.length > MAX_DISPLAYED_HINTS) { hints = hints.slice(0, MAX_DISPLAYED_HINTS); } return {hints: hints, needGuesses: needGuesses}; }; Session.prototype.setTernHints = function (newHints) { this.ternHints = newHints; }; Session.prototype.setGuesses = function (newGuesses) { this.ternGuesses = newGuesses; }; /** * Set a new function type hint. * * @param {Array<{name: string, type: string, isOptional: boolean}>} newFnType - * Array of function hints. */ Session.prototype.setFnType = function (newFnType) { this.fnType = newFnType; }; /** * The position of the function call for the current fnType. * * @param {{line:number, ch:number}} functionCallPos - the offset of the function call. */ Session.prototype.setFunctionCallPos = function (functionCallPos) { this.functionCallPos = functionCallPos; }; /** * Get the function type hint. This will format the hint, showing the * parameter at the cursor in bold. * * @return {{parameters: Array<{name: string, type: string, isOptional: boolean}>, * currentIndex: number}} An Object where the * "parameters" property is an array of parameter objects; * the "currentIndex" property index of the hint the cursor is on, may be * -1 if the cursor is on the function identifier. */ Session.prototype.getParameterHint = function () { var fnHint = this.fnType, cursor = this.getCursor(), token = this.getToken(this.functionCallPos), start = {line: this.functionCallPos.line, ch: token.start}, fragment = this.editor.document.getRange(start, {line: this.functionCallPos.line + 10, ch: 0}); var ast; try { ast = Acorn.parse(fragment); } catch (e) { ast = Acorn_Loose.parse_dammit(fragment, {}); } // find argument as cursor location and bold it. var startOffset = this.getOffsetFromCursor(start), cursorOffset = this.getOffsetFromCursor(cursor), offset = cursorOffset - startOffset, node = ast.body[0], currentArg = -1; if (node.type === "ExpressionStatement") { node = node.expression; if (node.type === "SequenceExpression") { node = node.expressions[0]; } if (node.type === "BinaryExpression") { if (node.left.type === "CallExpression") { node = node.left; } else if (node.right.type === "CallExpression") { node = node.right; } } if (node.type === "CallExpression") { var args = node["arguments"], i, n = args.length, lastEnd = offset, text; for (i = 0; i < n; i++) { node = args[i]; if (offset >= node.start && offset <= node.end) { currentArg = i; break; } else if (offset < node.start) { // The range of nodes can be disjoint so see i f we // passed the node. If we passed the node look at the // text between the nodes to figure out which // arg we are on. text = fragment.substring(lastEnd, node.start); // test if comma is before or after the offset if (text.indexOf(",") >= (offset - lastEnd)) { // comma is after the offset so the current arg is the // previous arg node. i--; } else if (i === 0 && text.indexOf("(") !== -1) { // the cursor is on the function identifier currentArg = -1; break; } currentArg = Math.max(0, i); break; } else if (i + 1 === n) { // look for a comma after the node.end. This will tell us we // are on the next argument, even there is no text, and therefore no node, // for the next argument. text = fragment.substring(node.end, offset); if (text.indexOf(",") !== -1) { currentArg = i + 1; // we know we are after the current arg, but keep looking } } lastEnd = node.end; } // if there are no args, then figure out if we are on the function identifier if (n === 0 && cursorOffset > this.getOffsetFromCursor(this.functionCallPos)) { currentArg = 0; } } } return {parameters: fnHint, currentIndex: currentArg}; }; /** * Get the javascript text of the file open in the editor for this Session. * For a javascript file, this is just the text of the file. For an HTML file, * this will be only the text in the ", from + 7); inc = 9; } else if (src.substr(from, 6).toLowerCase() === "", from + 6); inc = 8; } else { to = _find(src, ">", from + 1, true); inc = 1; } if (to < 0) { return null; } return {from: from, length: to + inc - from}; } /** Extract tag attributes from the given source of a single tag * @param {string} source content */ function _extractAttributes(content) { // remove the node name and the closing bracket and optional slash content = content.replace(/^<\S+\s*/, ""); content = content.replace(/\s*\/?>$/, ""); if (content.length === 0) { return; } // go through the items and identify key value pairs split by = var index, key, value; var attributes = {}; _findEach(content, [/\s/, 1], true, undefined, function each(item) { index = item.search("="); if (index < 0) { return; } // get the key key = item.substr(0, index).trim(); if (key.length === 0) { return; } // get the value value = item.substr(index + 1).trim(); value = _removeQuotes(value); attributes[key] = value; }); return attributes; } /** Extract the node payload * @param {string} source content */ function extractPayload(content) { var payload = {}; if (content[0] !== "<") { // text payload.nodeType = 3; payload.nodeValue = content; } else if (content.substr(0, 4) === " var TYPE_DOCUMENT = DOMNode.TYPE_DOCUMENT = 9; // document node /** Remove a node */ DOMNode.prototype.remove = function remove() { this.agent.removeNode(this); if (this.parent) { this.parent.removeChild(this); } }; /** Node Payload ***********************************************************/ /** Set the node payload * @param {DOM.Node} payload */ DOMNode.prototype.setPayload = function setPayload(payload) { this.nodeId = payload.nodeId; this.type = payload.nodeType; if (payload.nodeName) { this.name = payload.nodeName; } if (payload.nodeValue) { this.value = payload.nodeValue; } this.attributes = {}; if (payload.attributes) { var i, k, v; for (i = 0; i < payload.attributes.length; i += 2) { k = payload.attributes[i]; v = payload.attributes[i + 1]; this.attributes[k] = v; } } if (payload.sourceOffset) { this.location = payload.sourceOffset; } if (payload.sourceLength) { this.length = payload.sourceLength; } else { if (this.value) { this.length = this.value.length; } else if (this.name) { this.length = this.name.length + 2; } } if (payload.children) { this.setChildrenPayload(payload.children); } else if (payload.childNodeCount) { this.agent.requestChildNodes(this); } }; /** Create child nodes from the given payload * @param [{DOM.Node}] payload of the children */ DOMNode.prototype.setChildrenPayload = function setChildrenPayload(childrenPayload) { var i, payload, node; for (i in childrenPayload) { payload = childrenPayload[i]; node = new DOMNode(this.agent, payload); this.appendChild(node); } }; /** Construct the payload for this node */ DOMNode.prototype.payload = function payload() { var res = { type: this.type }; if (this.nodeType === TYPE_ELEMENT) { res.nodeName = this.name; } else { res.value = this.value; } return res; }; /** Find the next node that matches the given payload * @param {DOM.Node} payload */ DOMNode.prototype.findParentForNextNodeMatchingPayload = function findParentForNextNodeMatchingPayload(payload) { var parent = this.canHaveChildren() ? this : this.parent; while (parent && !parent.matchesPayload(payload)) { parent = parent.parent; } return parent; }; /** Find the next node that matches the given payload * @param {DOM.Node} payload */ DOMNode.prototype.findNextNodeMatchingPayload = function findNextNodeMatchingPayload(payload) { var next = this.nextNode(); while (next && !next.matchesPayload(payload)) { next = next.nextNode(); } return next; }; /** Test if the node matches the given payload * @param {DOM.Node} payload */ DOMNode.prototype.matchesPayload = function matchesPayload(payload) { var r = false; if (this.type === payload.nodeType) { switch (this.type) { case 1: r = this.name === payload.nodeName; break; case 3: // TODO payload.nodeValue's HTML Entities must be decoded // r = this.value === payload.nodeValue; r = true; break; default: r = true; } } // Useful output for debugging this - do not remove // console.debug(this.type + "," + this.name + "," + this.value + " = " + payload.nodeType + "," + payload.nodeName + "," + payload.value + " -> " + r); return r; }; /** Resolve the node and retrieve its objectId from the remote debugger */ DOMNode.prototype.resolve = function resolve() { var def = new $.Deferred(); if (this.objectId) { def.resolve(this); } else if (!this.nodeId) { def.reject(); } else { this.agent.resolveNode(this, function onResolve(res) { this.objectId = res.object.objectId; def.resolve(this); }.bind(this)); } return def.promise(); }; /** Tree Operations ******************************************************/ /** Can the node have children? */ DOMNode.prototype.canHaveChildren = function canHaveChildren() { return (this.type === 1 && !this.closed && !this.closing && this.nodeName !== "LINK"); }; /** Remove a child * @param {DOMNode} child node to remove */ DOMNode.prototype.removeChild = function removeChild(node) { this.children.splice(this.indexOfChild(node), 1); delete node.parent; }; /** Insert a child node at the given index * @param {DOMNode} node to insert * @param {integer} optional index (node is appended if missing) */ DOMNode.prototype.insertChildAt = function insertChildAt(node, index) { if (node.parent) { node.parent.removeChild(node); } if (!index || index < 0 || index > this.children.length) { index = this.children.length; } this.children.splice(index, 0, node); node.parent = this; return node; }; /** Append a child to this node * @param {DOMNode} child node to append */ DOMNode.prototype.appendChild = function appendChild(node) { return this.insertChildAt(node); }; /** Insert a child node after the given node * @param {DOMNode} child node to insert * @param {DOMNode} existing child node */ DOMNode.prototype.insertChildAfter = function insertChildAfter(node, sibling) { var index = this.indexOfChild(sibling); if (index >= 0) { index++; } return this.insertChildAt(node, index); }; /** Insert a child node before the given node * @param {DOMNode} child node to insert * @param {DOMNode} existing child node */ DOMNode.prototype.insertChildBefore = function insertChildBefore(node, sibling) { var index = this.indexOfChild(sibling); return this.insertChildAt(node, index); }; /** Determine the index of a child node * @param {DOMNode} child node */ DOMNode.prototype.indexOfChild = function indexOfChild(node) { if (!node) { return -1; } var i; for (i in this.children) { if (this.children[i] === node) { return parseInt(i, 0); } } return -1; }; /** Get the previous sibling */ DOMNode.prototype.previousSibling = function previousSibling() { if (!this.parent) { return null; } return this.parent.children[this.parent.indexOfChild(this) - 1]; }; /** Get the next sibling */ DOMNode.prototype.nextSibling = function nextSibling() { if (!this.parent) { return null; } return this.parent.children[this.parent.indexOfChild(this) + 1]; }; /** Get the previous node */ DOMNode.prototype.previousNode = function previousNode() { var node = this.previousSibling(); if (node) { if (node.children.length > 0) { node = node.children[node.children.length - 1]; } } else { node = this.parent; } return node; }; /** Get the next node */ DOMNode.prototype.nextNode = function nextNode() { if (this.children.length > 0) { // return the first child return this.children[0]; } // return this or any ancestor's next sibling var node, parent = this; while (parent) { node = parent.nextSibling(); if (node) { return node; } parent = parent.parent; } return null; }; /** Traverse the tree * @param {function({DOM.Node})} called for this node and all descendants */ DOMNode.prototype.each = function each(callback) { if (callback(this) === false) { return false; } var i; for (i in this.children) { if (this.children[i].each(callback) === false) { return false; } } return true; }; /** Find a node in the tree * @param {function} or {string} or {integer} find condition */ DOMNode.prototype.find = function find(match) { var findCondition = _makeFindCondition(match); var node = null; this.each(function each(n) { if (findCondition(n)) { node = n; return false; } }); return node; }; /** Find all nodes with the given find condition * @param {function} or {string} or {integer} find condition */ DOMNode.prototype.findAll = function findAll(match) { var nodes = []; var findCondition = _makeFindCondition(match); this.each(function each(node) { if (findCondition(node)) { nodes.push(node); } }); return nodes; }; /** Iterate over all parent nodes * @param {function({DOM.Node})} called for each ancestor */ DOMNode.prototype.eachParent = function eachParent(callback) { var node = this.parent; while (node) { if (callback(node) === false) { return; } node = node.parent; } return null; }; /** Find a parent node that matches the find condition * @param {function} or {string} or {integer} find condition */ DOMNode.prototype.findParent = function findParent(findCondition) { var theParent = null; this.eachParent(function each(parent) { if (findCondition(parent)) { theParent = parent; return false; } }); return theParent; }; /** Find the root of the tree */ DOMNode.prototype.root = function root() { var node = this; while (node.parent) { node = node.parent; } return node; }; /** Node Info ***********************************************************/ /** Test if the given location is inside this node * @param {integer} location * @param {boolean} also include children */ DOMNode.prototype.isAtLocation = function isAtLocation(location, includeChildren) { if (includeChildren === undefined) { includeChildren = true; } if (!this.location || location < this.location) { return false; } var to; if (includeChildren && this.closeLocation) { to = this.closeLocation + this.closeLength; } else { to = this.location + this.length; } if (this.type === TYPE_TEXT) { to += 1; } return location < to; }; /** Test if this node is empty */ DOMNode.prototype.isEmpty = function isEmpty() { return this.type === TYPE_TEXT && /^\s*$/.test(this.value); }; /** Debug Output */ DOMNode.prototype.toString = function toString() { var r; switch (this.type) { case TYPE_ELEMENT: r = "<" + this.name + ">"; break; case TYPE_ATTRIBUTE: r = "[ATTRIBUTE]"; break; case TYPE_TEXT: r = this.value.replace(/\s+/, " ").substr(0, 40); break; case TYPE_COMMENT: r = ""; break; case TYPE_DOCUMENT: r = ""; break; } return r; }; /** Detailed Debug Output */ DOMNode.prototype.dump = function dump(pre) { if (pre === undefined) { pre = ""; } var r = pre + this.toString(); if (this.location) { r = _fill(r, 60); r += " (" + this.location + "," + (this.location + this.length) + ")"; if (this.closeLocation) { r += " (" + this.closeLocation + "," + (this.closeLocation + this.closeLength) + ")"; } } if (this.nodeId) { r = _fill(r, 80); r += " {" + this.nodeId + "}"; } console.info(r); pre += ". "; var i; for (i in this.children) { this.children[i].dump(pre); } }; return DOMNode; }); ================================================ FILE: src/LiveDevelopment/Agents/EditAgent.js ================================================ /* * Copyright (c) 2012 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /** * EditAgent propagates changes from the in-document editor to the source * document. */ define(function EditAgent(require, exports, module) { "use strict"; var Inspector = require("LiveDevelopment/Inspector/Inspector"); var DOMAgent = require("LiveDevelopment/Agents/DOMAgent"); var RemoteAgent = require("LiveDevelopment/Agents/RemoteAgent"); var GotoAgent = require("LiveDevelopment/Agents/GotoAgent"); var EditorManager = require("editor/EditorManager"); var _editedNode; /** Find changed characters * @param {string} old value * @param {string} changed value * @return {from, to, text} */ function _findChangedCharacters(oldValue, value) { if (oldValue === value) { return undefined; } var length = oldValue.length; var index = 0; // find the first character that changed var i; for (i = 0; i < length; i++) { if (value[i] !== oldValue[i]) { break; } } index += i; value = value.substr(i); length -= i; // find the last character that changed for (i = 0; i < length; i++) { if (value[value.length - 1 - i] !== oldValue[oldValue.length - 1 - i]) { break; } } length -= i; value = value.substr(0, value.length - i); return { from: index, to: index + length, text: value }; } // WebInspector Event: DOM.characterDataModified function _onCharacterDataModified(event, res) { // res = {nodeId, characterData} if (_editedNode.nodeId !== res.nodeId) { return; } GotoAgent.open(DOMAgent.url); var editor = EditorManager.getCurrentFullEditor(); var codeMirror = editor._codeMirror; var change = _findChangedCharacters(_editedNode.value, res.characterData); if (change) { var from = codeMirror.posFromIndex(_editedNode.location + change.from); var to = codeMirror.posFromIndex(_editedNode.location + change.to); exports.isEditing = true; editor.document.replaceRange(change.text, from, to); exports.isEditing = false; var newPos = codeMirror.posFromIndex(_editedNode.location + change.from + change.text.length); editor.setCursorPos(newPos.line, newPos.ch); } } // Remote Event: Go to the given source node function _onRemoteEdit(event, res) { // res = {nodeId, name, value} // detach from DOM change events if (res.value === "0") { Inspector.DOM.off(".EditAgent"); return; } // find and store the edited node var node = DOMAgent.nodeWithId(res.nodeId); node = node.children[0]; if (!node.location) { return; } _editedNode = node; // attach to character data modified events Inspector.DOM.on("characterDataModified.EditAgent", _onCharacterDataModified); } /** Initialize the agent */ function load() { RemoteAgent.on("edit.EditAgent", _onRemoteEdit); } /** Initialize the agent */ function unload() { RemoteAgent.off(".EditAgent"); } // Export public functions exports.load = load; exports.unload = unload; }); ================================================ FILE: src/LiveDevelopment/Agents/GotoAgent.js ================================================ /* * Copyright (c) 2012 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /*jslint forin: true, regexp: true */ /** * GotoAgent constructs and responds to the in-browser goto dialog. */ define(function GotoAgent(require, exports, module) { "use strict"; require("utils/Global"); var Inspector = require("LiveDevelopment/Inspector/Inspector"), DOMAgent = require("LiveDevelopment/Agents/DOMAgent"), ScriptAgent = require("LiveDevelopment/Agents/ScriptAgent"), RemoteAgent = require("LiveDevelopment/Agents/RemoteAgent"), EditorManager = require("editor/EditorManager"), CommandManager = require("command/CommandManager"), Commands = require("command/Commands"); /** Return the URL without the query string * @param {string} URL */ function _urlWithoutQueryString(url) { var index = url.search(/[#\?]/); if (index >= 0) { url = url.substr(0, index); } return url; } /** Get the file component of the given url * @param {string} URL */ function _fileFromURL(url) { var comp = url.split("/"); return comp[comp.length - 1]; } /** Make the given node a target for goto * @param [] targets array * @param {DOMNode} node */ function _makeHTMLTarget(targets, node) { if (node.location) { var url = DOMAgent.url; var location = node.location; if (node.canHaveChildren()) { location += node.length; } url += ":" + location; var name = "<" + node.name + ">"; var file = _fileFromURL(url); targets.push({"type": "html", "url": url, "name": name, "file": file}); } } /** Make the given css rule a target for goto * @param [] targets array * @param {CSS.Rule} node */ function _makeCSSTarget(targets, rule) { if (rule.sourceURL) { var url = rule.sourceURL; url += ":" + rule.style.range.start; var name = rule.selectorList.text; var file = _fileFromURL(url); targets.push({"type": "css", "url": url, "name": name, "file": file}); } } /** Make the given javascript callFrame the target for goto * @param [] targets array * @param {Debugger.CallFrame} node */ function _makeJSTarget(targets, callFrame) { var script = ScriptAgent.scriptWithId(callFrame.location.scriptId); if (script && script.url) { var url = script.url; url += ":" + callFrame.location.lineNumber + "," + callFrame.location.columnNumber; var name = callFrame.functionName; if (name === "") { name = "anonymous function"; } var file = _fileFromURL(url); targets.push({"type": "js", "url": url, "name": name, "file": file}); } } /** Gather options where to go to from the given source node */ function _onRemoteShowGoto(event, res) { // res = {nodeId, name, value} var node = DOMAgent.nodeWithId(res.nodeId); // get all css rules that apply to the given node Inspector.CSS.getMatchedStylesForNode(node.nodeId, function onMatchedStyles(res) { var i, targets = []; _makeHTMLTarget(targets, node); for (i in node.trace) { _makeJSTarget(targets, node.trace[i]); } for (i in node.events) { var trace = node.events[i]; _makeJSTarget(targets, trace.callFrames[0]); } for (i in res.matchedCSSRules.reverse()) { _makeCSSTarget(targets, res.matchedCSSRules[i].rule); } RemoteAgent.call("showGoto", targets); }); } /** Point the master editor to the given location * @param {integer} location in file */ function openLocation(location, noFlash) { var editor = EditorManager.getCurrentFullEditor(); var codeMirror = editor._codeMirror; if (typeof location === "number") { location = codeMirror.posFromIndex(location); } codeMirror.setCursor(location); editor.focus(); if (!noFlash) { codeMirror.addLineClass(location.line, "wrap", "flash"); window.setTimeout(function () { codeMirror.removeLineClass(location.line, "wrap", "flash"); }, 1000); } } /** Open the editor at the given url and editor location * @param {string} url * @param {integer} optional location in file */ function open(url, location, noFlash) { console.assert(url.substr(0, 7) === "file://", "Cannot open non-file URLs"); var result = new $.Deferred(); url = _urlWithoutQueryString(url); // Extract the path, also strip the third slash when on Windows var path = url.slice(brackets.platform === "win" ? 8 : 7); // URL-decode the path ('%20' => ' ') path = decodeURI(path); var promise = CommandManager.execute(Commands.FILE_OPEN, {fullPath: path}); promise.done(function onDone(doc) { if (location) { openLocation(location, noFlash); } result.resolve(); }); promise.fail(function onErr(err) { console.error(err); result.reject(err); }); return result.promise(); } /** Go to the given source node */ function _onRemoteGoto(event, res) { // res = {nodeId, name, value} var location, url = res.value; var matches = /^(.*):([^:]+)$/.exec(url); if (matches) { url = matches[1]; location = matches[2].split(","); if (location.length === 1) { location = parseInt(location[0], 10); } else { location = { line: parseInt(location[0], 10), ch: parseInt(location[1], 10) }; } } open(url, location); } /** Initialize the agent */ function load() { RemoteAgent .on("showgoto.GotoAgent", _onRemoteShowGoto) .on("goto.GotoAgent", _onRemoteGoto); } /** Initialize the agent */ function unload() { RemoteAgent.off(".GotoAgent"); } // Export public functions exports.openLocation = openLocation; exports.open = open; exports.load = load; exports.unload = unload; }); ================================================ FILE: src/LiveDevelopment/Agents/HighlightAgent.js ================================================ /* * Copyright (c) 2012 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /** * HighlightAgent dispatches events for highlight requests from in-browser * highlight requests, and allows highlighting nodes and rules in the browser. * * Trigger "highlight" when a node should be highlighted */ define(function HighlightAgent(require, exports, module) { "use strict"; var DOMAgent = require("LiveDevelopment/Agents/DOMAgent"), EventDispatcher = require("utils/EventDispatcher"), Inspector = require("LiveDevelopment/Inspector/Inspector"), LiveDevelopment = require("LiveDevelopment/LiveDevelopment"), RemoteAgent = require("LiveDevelopment/Agents/RemoteAgent"), _ = require("thirdparty/lodash"); var _highlight = {}; // active highlight // Remote Event: Highlight function _onRemoteHighlight(event, res) { var node; if (res.value === "1") { node = DOMAgent.nodeWithId(res.nodeId); } exports.trigger("highlight", node); } /** Hide in-browser highlighting */ function hide() { switch (_highlight.type) { case "node": Inspector.DOM.hideHighlight(); break; case "css": RemoteAgent.call("hideHighlight"); break; } _highlight = {}; } /** Highlight a single node using DOM.highlightNode * @param {DOMNode} node */ function node(n) { if (!LiveDevelopment.config.experimental) { return; } if (!Inspector.config.highlight) { return; } // go to the parent of a text node if (n && n.type === 3) { n = n.parent; } // node cannot be highlighted if (!n || !n.nodeId || n.type !== 1) { return hide(); } // node is already highlighted if (_highlight.type === "node" && _highlight.ref === n.nodeId) { return; } // highlight the node _highlight = {type: "node", ref: n.nodeId}; Inspector.DOM.highlightNode(n.nodeId, Inspector.config.highlightConfig); } /** Highlight all nodes affected by a CSS rule * @param {string} rule selector */ function rule(name) { if (_highlight.ref === name) { return; } hide(); _highlight = {type: "css", ref: name}; RemoteAgent.call("highlightRule", name); } /** Highlight all nodes with 'data-brackets-id' value * that matches id, or if id is an array, matches any of the given ids. * @param {string|Array} value of the 'data-brackets-id' to match, * or an array of such. */ function domElement(ids) { var selector = ""; if (!Array.isArray(ids)) { ids = [ids]; } _.each(ids, function (id) { if (selector !== "") { selector += ","; } selector += "[data-brackets-id='" + id + "']"; }); rule(selector); } /** * Redraw active highlights */ function redraw() { RemoteAgent.call("redrawHighlights"); } /** Initialize the agent */ function load() { if (LiveDevelopment.config.experimental) { RemoteAgent.on("highlight.HighlightAgent", _onRemoteHighlight); } } /** Clean up */ function unload() { if (LiveDevelopment.config.experimental) { RemoteAgent.off(".HighlightAgent"); } } EventDispatcher.makeEventDispatcher(exports); // Export public functions exports.hide = hide; exports.node = node; exports.rule = rule; exports.domElement = domElement; exports.redraw = redraw; exports.load = load; exports.unload = unload; }); ================================================ FILE: src/LiveDevelopment/Agents/NetworkAgent.js ================================================ /* * Copyright (c) 2012 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /** * NetworkAgent tracks all resources loaded by the remote debugger. Use * `wasURLRequested(url)` to query whether a resource was loaded. */ define(function NetworkAgent(require, exports, module) { "use strict"; var Inspector = require("LiveDevelopment/Inspector/Inspector"); var _urlRequested = {}; // url -> request info /** Return the URL without the query string * @param {string} URL */ function _urlWithoutQueryString(url) { var index = url.search(/[#\?]/); if (index >= 0) { url = url.substr(0, index); } return url; } /** Return the resource information for a given URL * @param {string} url */ function wasURLRequested(url) { return _urlRequested && _urlRequested[url]; } function _logURL(url) { _urlRequested[_urlWithoutQueryString(url)] = true; } // WebInspector Event: Network.requestWillBeSent function _onRequestWillBeSent(event, res) { // res = {requestId, frameId, loaderId, documentURL, request, timestamp, initiator, stackTrace, redirectResponse} _logURL(res.request.url); } function _reset() { _urlRequested = {}; } // WebInspector Event: Page.frameNavigated function _onFrameNavigated(event, res) { // res = {frame} // Clear log when navigating to a new page, but not if an iframe was loaded if (!res.frame.parentId) { _reset(); } _logURL(res.frame.url); } /** * Enable the inspector Network domain * @return {jQuery.Promise} A promise resolved when the Network.enable() command is successful. */ function enable() { return Inspector.Network.enable(); } /** Initialize the agent */ function load() { Inspector.Page.on("frameNavigated.NetworkAgent", _onFrameNavigated); Inspector.Network.on("requestWillBeSent.NetworkAgent", _onRequestWillBeSent); } /** Unload the agent */ function unload() { _reset(); Inspector.Page.off(".NetworkAgent"); Inspector.Network.off(".NetworkAgent"); } // Export public functions exports.wasURLRequested = wasURLRequested; exports.enable = enable; exports.load = load; exports.unload = unload; }); ================================================ FILE: src/LiveDevelopment/Agents/RemoteAgent.js ================================================ /* * Copyright (c) 2012 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /*jslint regexp: true */ /** * RemoteAgent defines and provides an interface for custom remote functions * loaded from RemoteFunctions. Remote commands are executed via * `call(name, varargs)`. * * Remote events are dispatched as events on this object. */ define(function RemoteAgent(require, exports, module) { "use strict"; var LiveDevelopment = require("LiveDevelopment/LiveDevelopment"), EventDispatcher = require("utils/EventDispatcher"), Inspector = require("LiveDevelopment/Inspector/Inspector"), RemoteFunctions = require("text!LiveDevelopment/Agents/RemoteFunctions.js"), PreferencesManager = require("preferences/PreferencesManager"); var _load; // deferred load var _objectId; // the object id of the remote object var _intervalId; // interval used to send keepAlive events // WebInspector Event: DOM.attributeModified function _onAttributeModified(event, res) { // res = {nodeId, name, value} var matches = /^data-ld-(.*)/.exec(res.name); if (matches) { exports.trigger(matches[1], res); } } function _call(objectId, method, varargs) { console.assert(objectId, "Attempted to call remote method without objectId set."); var args = Array.prototype.slice.call(arguments, 2), callback, deferred = new $.Deferred(); // if the last argument is a function it is the callback function if (typeof args[args.length - 1] === "function") { callback = args.pop(); } // Resolve node parameters args = args.map(function (arg) { if (arg && arg.nodeId) { return arg.resolve(); } return arg; }); $.when.apply(undefined, args).done(function onResolvedAllNodes() { var params = []; args.forEach(function (arg) { if (arg.objectId) { params.push({objectId: arg.objectId}); } else { params.push({value: arg}); } }); Inspector.Runtime.callFunctionOn(objectId, method, params, undefined, callback) .then(deferred.resolve, deferred.reject); }); return deferred.promise(); } /** Call a remote function * The parameters are passed on to the remote functions. Nodes are resolved * and sent as objectIds. * @param {string} function name */ function call(method, varargs) { var argsArray = [_objectId, "_LD." + method]; if (arguments.length > 1) { argsArray = argsArray.concat(Array.prototype.slice.call(arguments, 1)); } return _call.apply(null, argsArray); } function _stopKeepAliveInterval() { if (_intervalId) { window.clearInterval(_intervalId); _intervalId = null; } } function _startKeepAliveInterval() { _stopKeepAliveInterval(); _intervalId = window.setInterval(function () { call("keepAlive"); }, 1000); } // WebInspector Event: Page.frameNavigated function _onFrameNavigated(event, res) { // res = {frame} // Re-inject RemoteFunctions when navigating to a new page, but not if an iframe was loaded if (res.frame.parentId) { return; } _stopKeepAliveInterval(); // inject RemoteFunctions var command = "window._LD=" + RemoteFunctions + "(" + JSON.stringify(LiveDevelopment.config) + "," + PreferencesManager.get("livedev.wsPort") + ");"; Inspector.Runtime.evaluate(command, function onEvaluate(response) { if (response.error || response.wasThrown) { _load.reject(response.error); } else { _objectId = response.result.objectId; _load.resolve(); _startKeepAliveInterval(); } }); } /** Initialize the agent */ function load() { _load = new $.Deferred(); Inspector.Page.on("frameNavigated.RemoteAgent", _onFrameNavigated); Inspector.Page.on("frameStartedLoading.RemoteAgent", _stopKeepAliveInterval); Inspector.DOM.on("attributeModified.RemoteAgent", _onAttributeModified); return _load.promise(); } /** Clean up */ function unload() { Inspector.Page.off(".RemoteAgent"); Inspector.DOM.off(".RemoteAgent"); _stopKeepAliveInterval(); } EventDispatcher.makeEventDispatcher(exports); // Export public functions exports.call = call; exports.load = load; exports.unload = unload; }); ================================================ FILE: src/LiveDevelopment/Agents/RemoteFunctions.js ================================================ /* * Copyright (c) 2012 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /*jslint forin: true */ /*global Node, MessageEvent */ /*theseus instrument: false */ /** * RemoteFunctions define the functions to be executed in the browser. This * modules should define a single function that returns an object of all * exported functions. */ function RemoteFunctions(config, remoteWSPort) { "use strict"; var experimental; if (!config) { experimental = false; } else { experimental = config.experimental; } var lastKeepAliveTime = Date.now(); var req, timeout; var animateHighlight = function (time) { if(req) { window.cancelAnimationFrame(req); window.clearTimeout(timeout); } req = window.requestAnimationFrame(redrawHighlights); timeout = setTimeout(function () { window.cancelAnimationFrame(req); req = null; }, time * 1000); }; /** * @type {DOMEditHandler} */ var _editHandler; var HIGHLIGHT_CLASSNAME = "__brackets-ld-highlight", KEEP_ALIVE_TIMEOUT = 3000; // Keep alive timeout value, in milliseconds // determine whether an event should be processed for Live Development function _validEvent(event) { if (window.navigator.platform.substr(0, 3) === "Mac") { // Mac return event.metaKey; } else { // Windows return event.ctrlKey; } } // determine the color for a type function _typeColor(type, highlight) { switch (type) { case "html": return highlight ? "#eec" : "#ffe"; case "css": return highlight ? "#cee" : "#eff"; case "js": return highlight ? "#ccf" : "#eef"; default: return highlight ? "#ddd" : "#eee"; } } // compute the screen offset of an element function _screenOffset(element) { var elemBounds = element.getBoundingClientRect(), body = window.document.body, offsetTop, offsetLeft; if (window.getComputedStyle(body).position === "static") { offsetLeft = elemBounds.left + window.pageXOffset; offsetTop = elemBounds.top + window.pageYOffset; } else { var bodyBounds = body.getBoundingClientRect(); offsetLeft = elemBounds.left - bodyBounds.left; offsetTop = elemBounds.top - bodyBounds.top; } return { left: offsetLeft, top: offsetTop }; } // set an event on a element function _trigger(element, name, value, autoRemove) { var key = "data-ld-" + name; if (value !== undefined && value !== null) { element.setAttribute(key, value); if (autoRemove) { window.setTimeout(element.removeAttribute.bind(element, key)); } } else { element.removeAttribute(key); } } // Checks if the element is in Viewport in the client browser function isInViewport(element) { var rect = element.getBoundingClientRect(); var html = window.document.documentElement; return ( rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || html.clientHeight) && rect.right <= (window.innerWidth || html.clientWidth) ); } // returns the distance from the top of the closest relatively positioned parent element function getDocumentOffsetTop(element) { return element.offsetTop + (element.offsetParent ? getDocumentOffsetTop(element.offsetParent) : 0); } // construct the info menu function Menu(element) { this.element = element; _trigger(this.element, "showgoto", 1, true); window.setTimeout(window.remoteShowGoto); this.remove = this.remove.bind(this); } Menu.prototype = { onClick: function (url, event) { event.preventDefault(); _trigger(this.element, "goto", url, true); this.remove(); }, createBody: function () { if (this.body) { return; } // compute the position on screen var offset = _screenOffset(this.element), x = offset.left, y = offset.top + this.element.offsetHeight; // create the container this.body = window.document.createElement("div"); this.body.style.setProperty("z-index", 2147483647); this.body.style.setProperty("position", "absolute"); this.body.style.setProperty("left", x + "px"); this.body.style.setProperty("top", y + "px"); this.body.style.setProperty("font-size", "11pt"); // draw the background this.body.style.setProperty("background", "#fff"); this.body.style.setProperty("border", "1px solid #888"); this.body.style.setProperty("-webkit-box-shadow", "2px 2px 6px 0px #ccc"); this.body.style.setProperty("border-radius", "6px"); this.body.style.setProperty("padding", "6px"); }, addItem: function (target) { var item = window.document.createElement("div"); item.style.setProperty("padding", "2px 6px"); if (this.body.childNodes.length > 0) { item.style.setProperty("border-top", "1px solid #ccc"); } item.style.setProperty("cursor", "pointer"); item.style.setProperty("background", _typeColor(target.type)); item.innerHTML = target.name; item.addEventListener("click", this.onClick.bind(this, target.url)); if (target.file) { var file = window.document.createElement("i"); file.style.setProperty("float", "right"); file.style.setProperty("margin-left", "12px"); file.innerHTML = " " + target.file; item.appendChild(file); } this.body.appendChild(item); }, show: function () { if (!this.body) { this.body = this.createBody(); } if (!this.body.parentNode) { window.document.body.appendChild(this.body); } window.document.addEventListener("click", this.remove); }, remove: function () { if (this.body && this.body.parentNode) { window.document.body.removeChild(this.body); } window.document.removeEventListener("click", this.remove); } }; function Editor(element) { this.onBlur = this.onBlur.bind(this); this.onKeyPress = this.onKeyPress.bind(this); this.element = element; this.element.setAttribute("contenteditable", "true"); this.element.focus(); this.element.addEventListener("blur", this.onBlur); this.element.addEventListener("keypress", this.onKeyPress); this.revertText = this.element.innerHTML; _trigger(this.element, "edit", 1); } Editor.prototype = { onBlur: function (event) { this.element.removeAttribute("contenteditable"); this.element.removeEventListener("blur", this.onBlur); this.element.removeEventListener("keypress", this.onKeyPress); _trigger(this.element, "edit", 0, true); }, onKeyPress: function (event) { switch (event.which) { case 13: // return this.element.blur(); break; case 27: // esc this.element.innerHTML = this.revertText; this.element.blur(); break; } } }; function Highlight(color, trigger) { this.color = color; this.trigger = !!trigger; this.elements = []; this.selector = ""; } Highlight.prototype = { _elementExists: function (element) { var i; for (i in this.elements) { if (this.elements[i] === element) { return true; } } return false; }, _makeHighlightDiv: function (element, doAnimation) { var elementBounds = element.getBoundingClientRect(), highlight = window.document.createElement("div"), elementStyling = window.getComputedStyle(element), transitionDuration = parseFloat(elementStyling.getPropertyValue('transition-duration')), animationDuration = parseFloat(elementStyling.getPropertyValue('animation-duration')); if (transitionDuration) { animateHighlight(transitionDuration); } if (animationDuration) { animateHighlight(animationDuration); } // Don't highlight elements with 0 width & height if (elementBounds.width === 0 && elementBounds.height === 0) { return; } var realElBorder = { right: elementStyling.getPropertyValue('border-right-width'), left: elementStyling.getPropertyValue('border-left-width'), top: elementStyling.getPropertyValue('border-top-width'), bottom: elementStyling.getPropertyValue('border-bottom-width') }; var borderBox = elementStyling.boxSizing === 'border-box'; var innerWidth = parseFloat(elementStyling.width), innerHeight = parseFloat(elementStyling.height), outerHeight = innerHeight, outerWidth = innerWidth; if (!borderBox) { innerWidth += parseFloat(elementStyling.paddingLeft) + parseFloat(elementStyling.paddingRight); innerHeight += parseFloat(elementStyling.paddingTop) + parseFloat(elementStyling.paddingBottom); outerWidth = innerWidth + parseFloat(realElBorder.right) + parseFloat(realElBorder.left), outerHeight = innerHeight + parseFloat(realElBorder.bottom) + parseFloat(realElBorder.top); } var visualisations = { horizontal: "left, right", vertical: "top, bottom" }; var drawPaddingRect = function(side) { var elStyling = {}; if (visualisations.horizontal.indexOf(side) >= 0) { elStyling['width'] = elementStyling.getPropertyValue('padding-' + side); elStyling['height'] = innerHeight + "px"; elStyling['top'] = 0; if (borderBox) { elStyling['height'] = innerHeight - parseFloat(realElBorder.top) - parseFloat(realElBorder.bottom) + "px"; } } else { elStyling['height'] = elementStyling.getPropertyValue('padding-' + side); elStyling['width'] = innerWidth + "px"; elStyling['left'] = 0; if (borderBox) { elStyling['width'] = innerWidth - parseFloat(realElBorder.left) - parseFloat(realElBorder.right) + "px"; } } elStyling[side] = 0; elStyling['position'] = 'absolute'; return elStyling; }; var drawMarginRect = function(side) { var elStyling = {}; var margin = []; margin['right'] = parseFloat(elementStyling.getPropertyValue('margin-right')); margin['top'] = parseFloat(elementStyling.getPropertyValue('margin-top')); margin['bottom'] = parseFloat(elementStyling.getPropertyValue('margin-bottom')); margin['left'] = parseFloat(elementStyling.getPropertyValue('margin-left')); if(visualisations['horizontal'].indexOf(side) >= 0) { elStyling['width'] = elementStyling.getPropertyValue('margin-' + side); elStyling['height'] = outerHeight + margin['top'] + margin['bottom'] + "px"; elStyling['top'] = "-" + (margin['top'] + parseFloat(realElBorder.top)) + "px"; } else { elStyling['height'] = elementStyling.getPropertyValue('margin-' + side); elStyling['width'] = outerWidth + "px"; elStyling['left'] = "-" + realElBorder.left; } elStyling[side] = "-" + (margin[side] + parseFloat(realElBorder[side])) + "px"; elStyling['position'] = 'absolute'; return elStyling; }; var setVisibility = function (el) { if ( !config.remoteHighlight.showPaddingMargin || parseInt(el.height, 10) <= 0 || parseInt(el.width, 10) <= 0 ) { el.display = 'none'; } else { el.display = 'block'; } }; var mainBoxStyles = config.remoteHighlight.stylesToSet; var paddingVisualisations = [ drawPaddingRect('top'), drawPaddingRect('right'), drawPaddingRect('bottom'), drawPaddingRect('left') ]; var marginVisualisations = [ drawMarginRect('top'), drawMarginRect('right'), drawMarginRect('bottom'), drawMarginRect('left') ]; var setupVisualisations = function (arr, config) { var i; for (i = 0; i < arr.length; i++) { setVisibility(arr[i]); // Applies to every visualisationElement (padding or margin div) arr[i]["transform"] = "none"; var el = window.document.createElement("div"), styles = Object.assign( {}, config, arr[i] ); _setStyleValues(styles, el.style); highlight.appendChild(el); } }; setupVisualisations( marginVisualisations, config.remoteHighlight.marginStyling ); setupVisualisations( paddingVisualisations, config.remoteHighlight.paddingStyling ); highlight.className = HIGHLIGHT_CLASSNAME; var offset = _screenOffset(element); var el = element, offsetLeft = 0, offsetTop = 0; // Probably the easiest way to get elements position without including transform do { offsetLeft += el.offsetLeft; offsetTop += el.offsetTop; el = el.offsetParent; } while(el); var stylesToSet = { "left": offsetLeft + "px", "top": offsetTop + "px", "width": innerWidth + "px", "height": innerHeight + "px", "z-index": 2000000, "margin": 0, "padding": 0, "position": "absolute", "pointer-events": "none", "box-shadow": "0 0 1px #fff", "box-sizing": elementStyling.getPropertyValue('box-sizing'), "border-right": elementStyling.getPropertyValue('border-right'), "border-left": elementStyling.getPropertyValue('border-left'), "border-top": elementStyling.getPropertyValue('border-top'), "border-bottom": elementStyling.getPropertyValue('border-bottom'), "transform": elementStyling.getPropertyValue('transform'), "transform-origin": elementStyling.getPropertyValue('transform-origin'), "border-color": config.remoteHighlight.borderColor }; var mergedStyles = Object.assign({}, stylesToSet, config.remoteHighlight.stylesToSet); var animateStartValues = config.remoteHighlight.animateStartValue; var animateEndValues = config.remoteHighlight.animateEndValue; var transitionValues = { "transition-property": "opacity, background-color, transform", "transition-duration": "300ms, 2.3s" }; function _setStyleValues(styleValues, obj) { var prop; for (prop in styleValues) { obj.setProperty(prop, styleValues[prop]); } } _setStyleValues(mergedStyles, highlight.style); _setStyleValues( doAnimation ? animateStartValues : animateEndValues, highlight.style ); if (doAnimation) { _setStyleValues(transitionValues, highlight.style); window.setTimeout(function () { _setStyleValues(animateEndValues, highlight.style); }, 20); } window.document.body.appendChild(highlight); }, add: function (element, doAnimation) { if (this._elementExists(element) || element === window.document) { return; } if (this.trigger) { _trigger(element, "highlight", 1); } if ((!window.event || window.event instanceof MessageEvent) && !isInViewport(element)) { var top = getDocumentOffsetTop(element); if (top) { top -= (window.innerHeight / 2); window.scrollTo(0, top); } } this.elements.push(element); this._makeHighlightDiv(element, doAnimation); }, clear: function () { var i, highlights = window.document.querySelectorAll("." + HIGHLIGHT_CLASSNAME), body = window.document.body; for (i = 0; i < highlights.length; i++) { body.removeChild(highlights[i]); } if (this.trigger) { for (i = 0; i < this.elements.length; i++) { _trigger(this.elements[i], "highlight", 0); } } this.elements = []; }, redraw: function () { var i, highlighted; // When redrawing a selector-based highlight, run a new selector // query to ensure we have the latest set of elements to highlight. if (this.selector) { highlighted = window.document.querySelectorAll(this.selector); } else { highlighted = this.elements.slice(0); } this.clear(); for (i = 0; i < highlighted.length; i++) { this.add(highlighted[i], false); } } }; var _currentEditor; function _toggleEditor(element) { _currentEditor = new Editor(element); } var _currentMenu; function _toggleMenu(element) { if (_currentMenu) { _currentMenu.remove(); } _currentMenu = new Menu(element); } var _localHighlight; var _remoteHighlight; var _setup = false; /** Event Handlers ***********************************************************/ function onMouseOver(event) { if (_validEvent(event)) { _localHighlight.add(event.target, true); } } function onMouseOut(event) { if (_validEvent(event)) { _localHighlight.clear(); } } function onMouseMove(event) { onMouseOver(event); window.document.removeEventListener("mousemove", onMouseMove); } function onClick(event) { if (_validEvent(event)) { event.preventDefault(); event.stopPropagation(); if (event.altKey) { _toggleEditor(event.target); } else { _toggleMenu(event.target); } } } function onKeyUp(event) { if (_setup && !_validEvent(event)) { window.document.removeEventListener("keyup", onKeyUp); window.document.removeEventListener("mouseover", onMouseOver); window.document.removeEventListener("mouseout", onMouseOut); window.document.removeEventListener("mousemove", onMouseMove); window.document.removeEventListener("click", onClick); _localHighlight.clear(); _localHighlight = undefined; _setup = false; } } function onKeyDown(event) { if (!_setup && _validEvent(event)) { window.document.addEventListener("keyup", onKeyUp); window.document.addEventListener("mouseover", onMouseOver); window.document.addEventListener("mouseout", onMouseOut); window.document.addEventListener("mousemove", onMouseMove); window.document.addEventListener("click", onClick); _localHighlight = new Highlight("#ecc", true); _setup = true; } } /** Public Commands **********************************************************/ // keep alive. Called once a second when a Live Development connection is active. // If several seconds have passed without this method being called, we can assume // that the connection has been severed and we should remove all our code/hooks. function keepAlive() { lastKeepAliveTime = Date.now(); } // show goto function showGoto(targets) { if (!_currentMenu) { return; } _currentMenu.createBody(); var i; for (i in targets) { _currentMenu.addItem(targets[i]); } _currentMenu.show(); } // remove active highlights function hideHighlight() { if (_remoteHighlight) { _remoteHighlight.clear(); _remoteHighlight = null; } } // highlight a node function highlight(node, clear) { if (!_remoteHighlight) { _remoteHighlight = new Highlight("#cfc"); } if (clear) { _remoteHighlight.clear(); } _remoteHighlight.add(node, true); } // highlight a rule function highlightRule(rule) { hideHighlight(); var i, nodes = window.document.querySelectorAll(rule); for (i = 0; i < nodes.length; i++) { highlight(nodes[i]); } _remoteHighlight.selector = rule; } // redraw active highlights function redrawHighlights() { if (_remoteHighlight) { _remoteHighlight.redraw(); } } window.addEventListener("resize", redrawHighlights); // Add a capture-phase scroll listener to update highlights when // any element scrolls. function _scrollHandler(e) { // Document scrolls can be updated immediately. Any other scrolls // need to be updated on a timer to ensure the layout is correct. if (e.target === window.document) { redrawHighlights(); } else { if (_remoteHighlight || _localHighlight) { window.setTimeout(redrawHighlights, 0); } } } window.addEventListener("scroll", _scrollHandler, true); var aliveTest = window.setInterval(function () { if (Date.now() > lastKeepAliveTime + KEEP_ALIVE_TIMEOUT) { // Remove highlights hideHighlight(); // Remove listeners window.removeEventListener("resize", redrawHighlights); window.removeEventListener("scroll", _scrollHandler, true); // Clear this interval window.clearInterval(aliveTest); } }, 1000); /** * Constructor * @param {Document} htmlDocument */ function DOMEditHandler(htmlDocument) { this.htmlDocument = htmlDocument; this.rememberedNodes = null; this.entityParseParent = htmlDocument.createElement("div"); } /** * @private * Find the first matching element with the specified data-brackets-id * @param {string} id * @return {Element} */ DOMEditHandler.prototype._queryBracketsID = function (id) { if (!id) { return null; } if (this.rememberedNodes && this.rememberedNodes[id]) { return this.rememberedNodes[id]; } var results = this.htmlDocument.querySelectorAll("[data-brackets-id='" + id + "']"); return results && results[0]; }; /** * @private * Insert a new child element * @param {Element} targetElement Parent element already in the document * @param {Element} childElement New child element * @param {Object} edit */ DOMEditHandler.prototype._insertChildNode = function (targetElement, childElement, edit) { var before = this._queryBracketsID(edit.beforeID), after = this._queryBracketsID(edit.afterID); if (edit.firstChild) { before = targetElement.firstChild; } else if (edit.lastChild) { after = targetElement.lastChild; } if (before) { targetElement.insertBefore(childElement, before); } else if (after && (after !== targetElement.lastChild)) { targetElement.insertBefore(childElement, after.nextSibling); } else { targetElement.appendChild(childElement); } }; /** * @private * Given a string containing encoded entity references, returns the string with the entities decoded. * @param {string} text The text to parse. * @return {string} The decoded text. */ DOMEditHandler.prototype._parseEntities = function (text) { // Kind of a hack: just set the innerHTML of a div to the text, which will parse the entities, then // read the content out. var result; this.entityParseParent.innerHTML = text; result = this.entityParseParent.textContent; this.entityParseParent.textContent = ""; return result; }; /** * @private * @param {Node} node * @return {boolean} true if node expects its content to be raw text (not parsed for entities) according to the HTML5 spec. */ function _isRawTextNode(node) { return (node.nodeType === Node.ELEMENT_NODE && /script|style|noscript|noframes|noembed|iframe|xmp/i.test(node.tagName)); } /** * @private * Replace a range of text and comment nodes with an optional new text node * @param {Element} targetElement * @param {Object} edit */ DOMEditHandler.prototype._textReplace = function (targetElement, edit) { function prevIgnoringHighlights(node) { do { node = node.previousSibling; } while (node && node.className === HIGHLIGHT_CLASSNAME); return node; } function nextIgnoringHighlights(node) { do { node = node.nextSibling; } while (node && node.className === HIGHLIGHT_CLASSNAME); return node; } function lastChildIgnoringHighlights(node) { node = (node.childNodes.length ? node.childNodes.item(node.childNodes.length - 1) : null); if (node && node.className === HIGHLIGHT_CLASSNAME) { node = prevIgnoringHighlights(node); } return node; } var start = (edit.afterID) ? this._queryBracketsID(edit.afterID) : null, startMissing = edit.afterID && !start, end = (edit.beforeID) ? this._queryBracketsID(edit.beforeID) : null, endMissing = edit.beforeID && !end, moveNext = start && nextIgnoringHighlights(start), current = moveNext || (end && prevIgnoringHighlights(end)) || lastChildIgnoringHighlights(targetElement), next, textNode = (edit.content !== undefined) ? this.htmlDocument.createTextNode(_isRawTextNode(targetElement) ? edit.content : this._parseEntities(edit.content)) : null, lastRemovedWasText, isText; // remove all nodes inside the range while (current && (current !== end)) { isText = current.nodeType === Node.TEXT_NODE; // if start is defined, delete following text nodes // if start is not defined, delete preceding text nodes next = (moveNext) ? nextIgnoringHighlights(current) : prevIgnoringHighlights(current); // only delete up to the nearest element. // if the start/end tag was deleted in a prior edit, stop removing // nodes when we hit adjacent text nodes if ((current.nodeType === Node.ELEMENT_NODE) || ((startMissing || endMissing) && (isText && lastRemovedWasText))) { break; } else { lastRemovedWasText = isText; if (current.remove) { current.remove(); } else if (current.parentNode && current.parentNode.removeChild) { current.parentNode.removeChild(current); } current = next; } } if (textNode) { // OK to use nextSibling here (not nextIgnoringHighlights) because we do literally // want to insert immediately after the start tag. if (start && start.nextSibling) { targetElement.insertBefore(textNode, start.nextSibling); } else if (end) { targetElement.insertBefore(textNode, end); } else { targetElement.appendChild(textNode); } } }; /** * @private * Apply an array of DOM edits to the document * @param {Array.} edits */ DOMEditHandler.prototype.apply = function (edits) { var targetID, targetElement, childElement, self = this; this.rememberedNodes = {}; edits.forEach(function (edit) { var editIsSpecialTag = edit.type === "elementInsert" && (edit.tag === "html" || edit.tag === "head" || edit.tag === "body"); if (edit.type === "rememberNodes") { edit.tagIDs.forEach(function (tagID) { var node = self._queryBracketsID(tagID); self.rememberedNodes[tagID] = node; if (node.remove) { node.remove(); } else if (node.parentNode && node.parentNode.removeChild) { node.parentNode.removeChild(node); } }); return; } targetID = edit.type.match(/textReplace|textDelete|textInsert|elementInsert|elementMove/) ? edit.parentID : edit.tagID; targetElement = self._queryBracketsID(targetID); if (!targetElement && !editIsSpecialTag) { console.error("data-brackets-id=" + targetID + " not found"); return; } switch (edit.type) { case "attrChange": case "attrAdd": targetElement.setAttribute(edit.attribute, self._parseEntities(edit.value)); break; case "attrDelete": targetElement.removeAttribute(edit.attribute); break; case "elementDelete": if (targetElement.remove) { targetElement.remove(); } else if (targetElement.parentNode && targetElement.parentNode.removeChild) { targetElement.parentNode.removeChild(targetElement); } break; case "elementInsert": childElement = null; if (editIsSpecialTag) { // If we already have one of these elements (which we should), then // just copy the attributes and set the ID. childElement = self.htmlDocument[edit.tag === "html" ? "documentElement" : edit.tag]; if (!childElement) { // Treat this as a normal insertion. editIsSpecialTag = false; } } if (!editIsSpecialTag) { childElement = self.htmlDocument.createElement(edit.tag); } Object.keys(edit.attributes).forEach(function (attr) { childElement.setAttribute(attr, self._parseEntities(edit.attributes[attr])); }); childElement.setAttribute("data-brackets-id", edit.tagID); if (!editIsSpecialTag) { self._insertChildNode(targetElement, childElement, edit); } break; case "elementMove": childElement = self._queryBracketsID(edit.tagID); self._insertChildNode(targetElement, childElement, edit); break; case "textInsert": var textElement = self.htmlDocument.createTextNode(_isRawTextNode(targetElement) ? edit.content : self._parseEntities(edit.content)); self._insertChildNode(targetElement, textElement, edit); break; case "textReplace": case "textDelete": self._textReplace(targetElement, edit); break; } }); this.rememberedNodes = {}; // update highlight after applying diffs redrawHighlights(); }; function applyDOMEdits(edits) { _editHandler.apply(edits); } /** * * @param {Element} elem */ function _domElementToJSON(elem) { var json = { tag: elem.tagName.toLowerCase(), attributes: {}, children: [] }, i, len, node, value; len = elem.attributes.length; for (i = 0; i < len; i++) { node = elem.attributes.item(i); value = (node.name === "data-brackets-id") ? parseInt(node.value, 10) : node.value; json.attributes[node.name] = value; } len = elem.childNodes.length; for (i = 0; i < len; i++) { node = elem.childNodes.item(i); // ignores comment nodes and visuals generated by live preview if (node.nodeType === Node.ELEMENT_NODE && node.className !== HIGHLIGHT_CLASSNAME) { json.children.push(_domElementToJSON(node)); } else if (node.nodeType === Node.TEXT_NODE) { json.children.push({ content: node.nodeValue }); } } return json; } function getSimpleDOM() { return JSON.stringify(_domElementToJSON(window.document.documentElement)); } function updateConfig(newConfig) { config = JSON.parse(newConfig); return JSON.stringify(config); } // init _editHandler = new DOMEditHandler(window.document); if (experimental) { window.document.addEventListener("keydown", onKeyDown); } var _ws = null; function onDocumentClick(event) { var element = event.target, currentDataId, newDataId; if (_ws && element && element.hasAttribute('data-brackets-id')) { _ws.send(JSON.stringify({ type: "message", message: element.getAttribute('data-brackets-id') })); } } function createWebSocket() { _ws = new WebSocket("ws://localhost:" + remoteWSPort); _ws.onopen = function () { window.document.addEventListener("click", onDocumentClick); }; _ws.onmessage = function (evt) { }; _ws.onclose = function () { // websocket is closed window.document.removeEventListener("click", onDocumentClick); }; } if (remoteWSPort) { createWebSocket(); } return { "DOMEditHandler" : DOMEditHandler, "keepAlive" : keepAlive, "showGoto" : showGoto, "hideHighlight" : hideHighlight, "highlight" : highlight, "highlightRule" : highlightRule, "redrawHighlights" : redrawHighlights, "applyDOMEdits" : applyDOMEdits, "getSimpleDOM" : getSimpleDOM, "updateConfig" : updateConfig }; } ================================================ FILE: src/LiveDevelopment/Agents/ScriptAgent.js ================================================ /* * Copyright (c) 2012 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /** * ScriptAgent tracks all executed scripts, defines internal breakpoints, and * interfaces with the remote debugger. */ define(function ScriptAgent(require, exports, module) { "use strict"; var Inspector = require("LiveDevelopment/Inspector/Inspector"); var DOMAgent = require("LiveDevelopment/Agents/DOMAgent"); var _load; // the load promise var _urlToScript; // url -> script info var _idToScript; // id -> script info var _insertTrace; // the last recorded trace of a DOM insertion // TODO: should the parameter to this be an ID rather than a URL? /** Get the script information for a given url * @param {string} url */ function scriptWithId(url) { return _idToScript[url]; } // TODO: Strip off query/hash strings from URL (see CSSAgent._canonicalize()) /** Get the script information for a given url * @param {string} url */ function scriptForURL(url) { return _urlToScript[url]; } // DOMAgent Event: Document root loaded function _onGetDocument(event, res) { Inspector.DOMDebugger.setDOMBreakpoint(res.root.nodeId, "subtree-modified"); _load.resolve(); } // WebInspector Event: DOM.childNodeInserted function _onChildNodeInserted(event, res) { // res = {parentNodeId, previousNodeId, node} if (_insertTrace) { var node = DOMAgent.nodeWithId(res.node.nodeId); node.trace = _insertTrace; _insertTrace = undefined; } } // TODO: Strip off query/hash strings from URL (see CSSAgent._canonicalize()) // WebInspector Event: Debugger.scriptParsed function _onScriptParsed(event, res) { // res = {scriptId, url, startLine, startColumn, endLine, endColumn, isContentScript, sourceMapURL} _idToScript[res.scriptId] = res; _urlToScript[res.url] = res; } // WebInspector Event: Debugger.scriptFailedToParse function _onScriptFailedToParse(event, res) { // res = {url, scriptSource, startLine, errorLine, errorMessage} } // WebInspector Event: Debugger.paused function _onPaused(event, res) { // res = {callFrames, reason, data} switch (res.reason) { // Exception case "exception": Inspector.Debugger.resume(); // var callFrame = res.callFrames[0]; // var script = scriptWithId(callFrame.location.scriptId); break; // DOMBreakpoint case "DOM": Inspector.Debugger.resume(); if (res.data.type === "subtree-modified" && res.data.insertion === true) { _insertTrace = res.callFrames; } break; } } function _reset() { _urlToScript = {}; _idToScript = {}; } /** * @private * WebInspector Event: Page.frameNavigated * @param {jQuery.Event} event * @param {frame: Frame} res */ function _onFrameNavigated(event, res) { // Clear maps when navigating to a new page, but not if an iframe was loaded if (!res.frame.parentId) { _reset(); } } /** Initialize the agent */ function load() { _reset(); _load = new $.Deferred(); var enableResult = new $.Deferred(); Inspector.Debugger.enable().done(function () { Inspector.Debugger.setPauseOnExceptions("uncaught").done(function () { enableResult.resolve(); }); }); Inspector.Page.on("frameNavigated.ScriptAgent", _onFrameNavigated); DOMAgent.on("getDocument.ScriptAgent", _onGetDocument); Inspector.Debugger .on("scriptParsed.ScriptAgent", _onScriptParsed) .on("scriptFailedToParse.ScriptAgent", _onScriptFailedToParse) .on("paused.ScriptAgent", _onPaused); Inspector.DOM.on("childNodeInserted.ScriptAgent", _onChildNodeInserted); return $.when(_load.promise(), enableResult.promise()); } /** Clean up */ function unload() { _reset(); Inspector.Page.off(".ScriptAgent"); DOMAgent.off(".ScriptAgent"); Inspector.Debugger.off(".ScriptAgent"); Inspector.DOM.off(".ScriptAgent"); } // Export public functions exports.scriptWithId = scriptWithId; exports.scriptForURL = scriptForURL; exports.load = load; exports.unload = unload; }); ================================================ FILE: src/LiveDevelopment/Documents/CSSDocument.js ================================================ /* * Copyright (c) 2012 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /*jslint forin: true */ /** * CSSDocument manages a single CSS source document * * __EDITING__ * * Editing the document will cause the style sheet to be reloaded via the * CSSAgent, which immediately updates the appearance of the rendered document. * * __HIGHLIGHTING__ * * CSSDocument supports highlighting nodes from the HighlightAgent and * highlighting all DOMNode corresponding to the rule at the cursor position * in the editor. * * __EVENTS__ * * CSSDocument dispatches these events: * * - deleted -- When the file for the underlying Document has been deleted. * The 2nd argument to the listener will be this CSSDocument. */ define(function CSSDocumentModule(require, exports, module) { "use strict"; var _ = require("thirdparty/lodash"), EventDispatcher = require("utils/EventDispatcher"), CSSAgent = require("LiveDevelopment/Agents/CSSAgent"), CSSUtils = require("language/CSSUtils"), EditorManager = require("editor/EditorManager"), HighlightAgent = require("LiveDevelopment/Agents/HighlightAgent"), Inspector = require("LiveDevelopment/Inspector/Inspector"); /** * @constructor * @param {!Document} doc The source document from Brackets * @param {!Editor} editor The editor for this document */ var CSSDocument = function CSSDocument(doc, editor) { this.doc = doc; this._highlight = []; this.onHighlight = this.onHighlight.bind(this); this.onCursorActivity = this.onCursorActivity.bind(this); // Add a ref to the doc since we're listening for change events this.doc.addRef(); this.onChange = this.onChange.bind(this); this.onDeleted = this.onDeleted.bind(this); this.doc.on("change.CSSDocument", this.onChange); this.doc.on("deleted.CSSDocument", this.onDeleted); this.onActiveEditorChange = this.onActiveEditorChange.bind(this); EditorManager.on("activeEditorChange", this.onActiveEditorChange); if (editor) { // Attach now this.attachToEditor(editor); } }; EventDispatcher.makeEventDispatcher(CSSDocument.prototype); /** * @private * Get the CSSStyleSheetHeader for this document */ CSSDocument.prototype._getStyleSheetHeader = function () { return CSSAgent.styleForURL(this.doc.url); }; /** * Get the browser version of the source * @return {jQuery.promise} Promise resolved with the text content of this CSS document */ CSSDocument.prototype.getSourceFromBrowser = function getSourceFromBrowser() { function getOnlyValue(obj) { var key; for (key in obj) { if (_.has(obj, key)) { return obj[key]; } } return null; } var deferred = new $.Deferred(), styleSheetHeader = this._getStyleSheetHeader(), styleSheet = getOnlyValue(styleSheetHeader); if (styleSheet) { Inspector.CSS.getStyleSheetText(styleSheet.styleSheetId).then(function (res) { deferred.resolve(res.text); }, deferred.reject); } else { deferred.reject(); } return deferred.promise(); }; /** Close the document */ CSSDocument.prototype.close = function close() { this.doc.off(".CSSDocument"); EditorManager.off("activeEditorChange", this.onActiveEditorChange); this.doc.releaseRef(); this.detachFromEditor(); }; /** * @private * Update the style sheet text content and redraw highlights */ CSSDocument.prototype._updateBrowser = function () { var reloadPromise = CSSAgent.reloadCSSForDocument(this.doc); if (Inspector.config.highlight) { reloadPromise.done(HighlightAgent.redraw); } }; CSSDocument.prototype.attachToEditor = function (editor) { this.editor = editor; if (this.editor) { HighlightAgent.on("highlight", this.onHighlight); this.editor.on("cursorActivity.CSSDocument", this.onCursorActivity); this.updateHighlight(); } }; CSSDocument.prototype.detachFromEditor = function () { if (this.editor) { HighlightAgent.hide(); HighlightAgent.off("highlight", this.onHighlight); this.editor.off(".CSSDocument"); this.onHighlight(); this.editor = null; } }; CSSDocument.prototype.updateHighlight = function () { if (Inspector.config.highlight && this.editor) { var editor = this.editor, selectors = []; _.each(this.editor.getSelections(), function (sel) { var selector = CSSUtils.findSelectorAtDocumentPos(editor, (sel.reversed ? sel.end : sel.start)); if (selector) { selectors.push(selector); } }); if (selectors.length) { HighlightAgent.rule(selectors.join(",")); } else { HighlightAgent.hide(); } } }; /** * Enable instrumented CSS * @param enabled {boolean} */ CSSDocument.prototype.setInstrumentationEnabled = function setInstrumentationEnabled(enabled) { // no-op // "Instrumentation" is always enabled for CSS, we make no modifications }; /** * Returns true if document edits appear live in the connected browser * @return {boolean} */ CSSDocument.prototype.isLiveEditingEnabled = function () { return true; }; /** * Returns a JSON object with HTTP response overrides * @return {{body: string}} */ CSSDocument.prototype.getResponseData = function getResponseData(enabled) { // Serve up the in-memory text, including any unsaved changes return { body: this.doc.getText() }; }; /** Event Handlers *******************************************************/ /** Triggered on cursor activity of the editor */ CSSDocument.prototype.onCursorActivity = function onCursorActivity(event, editor) { this.updateHighlight(); }; /** Triggered whenever the Document is edited */ CSSDocument.prototype.onChange = function onChange(event, editor, change) { this._updateBrowser(); }; /** Triggered if the Document's file is deleted */ CSSDocument.prototype.onDeleted = function onDeleted(event, editor, change) { // clear the CSS CSSAgent.clearCSSForDocument(this.doc); // shut down, since our Document is now dead this.close(); this.trigger("deleted", this); }; /** Triggered when the active editor changes */ CSSDocument.prototype.onActiveEditorChange = function (event, newActive, oldActive) { this.detachFromEditor(); if (newActive && newActive.document === this.doc) { this.attachToEditor(newActive); } }; /** Triggered by the HighlightAgent to highlight a node in the editor */ CSSDocument.prototype.onHighlight = function onHighlight(event, node) { // clear an existing highlight var i; for (i in this._highlight) { this._highlight[i].clear(); } this._highlight = []; if (!node || !node.location) { return; } // WebInspector Command: CSS.getMatchedStylesForNode Inspector.CSS.getMatchedStylesForNode(node.nodeId, function onGetMatchesStyles(res) { // res = {matchedCSSRules, pseudoElements, inherited} var codeMirror = this.editor._codeMirror, styleSheetIds = this._getStyleSheetHeader(); var i, rule, from, to; for (i in res.matchedCSSRules) { rule = res.matchedCSSRules[i]; if (rule.ruleId && styleSheetIds[rule.ruleId.styleSheetId]) { from = codeMirror.posFromIndex(rule.selectorRange.start); to = codeMirror.posFromIndex(rule.style.range.end); this._highlight.push(codeMirror.markText(from, to, { className: "highlight" })); } } }.bind(this)); }; // Export the class module.exports = CSSDocument; }); ================================================ FILE: src/LiveDevelopment/Documents/CSSPreprocessorDocument.js ================================================ /* * Copyright (c) 2014 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /** * CSSPreprocessorDocument manages a single LESS or SASS source document * * __HIGHLIGHTING__ * * CSSPreprocessorDocument supports highlighting all DOMNode corresponding to the rule at * the cursor position in the editor. * */ define(function CSSPreprocessorDocumentModule(require, exports, module) { "use strict"; var _ = require("thirdparty/lodash"), EventDispatcher = require("utils/EventDispatcher"), CSSUtils = require("language/CSSUtils"), EditorManager = require("editor/EditorManager"), HighlightAgent = require("LiveDevelopment/Agents/HighlightAgent"), Inspector = require("LiveDevelopment/Inspector/Inspector"); /** * @constructor * @param {!Document} doc The source document from Brackets * @param {?Editor} editor The editor for this document. This is not used here since * we always need to get the active editor for a preprocessor document * and not the one passed in `editor`. */ var CSSPreprocessorDocument = function CSSPreprocessorDocument(doc, editor) { this.doc = doc; this.onCursorActivity = this.onCursorActivity.bind(this); // Add a ref to the doc since we're listening for change events this.doc.addRef(); this.onActiveEditorChange = this.onActiveEditorChange.bind(this); EditorManager.on("activeEditorChange", this.onActiveEditorChange); this.onActiveEditorChange(null, EditorManager.getActiveEditor(), null); }; // CSSPreprocessorDocument doesn't dispatch events, but the "live document" interface requires an on() API EventDispatcher.makeEventDispatcher(CSSPreprocessorDocument.prototype); /** Close the document */ CSSPreprocessorDocument.prototype.close = function close() { this.doc.off(".CSSPreprocessorDocument"); EditorManager.off("activeEditorChange", this.onActiveEditorChange); this.doc.releaseRef(); this.detachFromEditor(); }; /** Return false so edits cause "out of sync" icon to appear */ CSSPreprocessorDocument.prototype.isLiveEditingEnabled = function () { // Normally this isn't called since wasURLRequested() returns false for us, but if user's // page uses less.js to dynamically load LESS files, then it'll be true and we'll get called. return false; }; CSSPreprocessorDocument.prototype.attachToEditor = function (editor) { this.editor = editor; if (this.editor) { this.editor.on("cursorActivity.CSSPreprocessorDocument", this.onCursorActivity); this.updateHighlight(); } }; CSSPreprocessorDocument.prototype.detachFromEditor = function () { if (this.editor) { HighlightAgent.hide(); this.editor.off(".CSSPreprocessorDocument"); this.editor = null; } }; CSSPreprocessorDocument.prototype.updateHighlight = function () { if (Inspector.config.highlight && this.editor) { var editor = this.editor, selectors = []; _.each(this.editor.getSelections(), function (sel) { var selector = CSSUtils.findSelectorAtDocumentPos(editor, (sel.reversed ? sel.end : sel.start)); if (selector) { selectors.push(selector); } }); if (selectors.length) { HighlightAgent.rule(selectors.join(",")); } else { HighlightAgent.hide(); } } }; /** Event Handlers *******************************************************/ /** Triggered on cursor activity of the editor */ CSSPreprocessorDocument.prototype.onCursorActivity = function onCursorActivity(event, editor) { this.updateHighlight(); }; /** Triggered when the active editor changes */ CSSPreprocessorDocument.prototype.onActiveEditorChange = function (event, newActive, oldActive) { this.detachFromEditor(); if (newActive && newActive.document === this.doc) { this.attachToEditor(newActive); } }; // Export the class module.exports = CSSPreprocessorDocument; }); ================================================ FILE: src/LiveDevelopment/Documents/HTMLDocument.js ================================================ /* * Copyright (c) 2012 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /** * HTMLDocument manages a single HTML source document * * __EDITING__ * * Editing the document will cause the corresponding node to be updated * by calling `applyChanges` on the DOMAgent. This will only work for * altering text nodes and will break when attempting to change DOM elements * or inserting or deleting nodes. * * __HIGHLIGHTING__ * * HTMLDocument supports highlighting nodes from the HighlightAgent and * highlighting the DOMNode corresponding to the cursor position in the * editor. */ define(function HTMLDocumentModule(require, exports, module) { "use strict"; var EditorManager = require("editor/EditorManager"), EventDispatcher = require("utils/EventDispatcher"), HighlightAgent = require("LiveDevelopment/Agents/HighlightAgent"), HTMLInstrumentation = require("language/HTMLInstrumentation"), Inspector = require("LiveDevelopment/Inspector/Inspector"), LiveDevelopment = require("LiveDevelopment/LiveDevelopment"), PerfUtils = require("utils/PerfUtils"), RemoteAgent = require("LiveDevelopment/Agents/RemoteAgent"), _ = require("thirdparty/lodash"); /** * @constructor * @param {!Document} doc The source document from Brackets * @param {!Editor} editor The editor for this document */ var HTMLDocument = function HTMLDocument(doc, editor) { this.doc = doc; if (this.doc) { this.doc.addRef(); } this.editor = editor; this._instrumentationEnabled = false; this._onActiveEditorChange = this._onActiveEditorChange.bind(this); EditorManager.on("activeEditorChange", this._onActiveEditorChange); // Attach now this.attachToEditor(editor); }; EventDispatcher.makeEventDispatcher(HTMLDocument.prototype); /** * Enable or disable instrumented HTML * @param {boolean} enabled Whether to enable or disable */ HTMLDocument.prototype.setInstrumentationEnabled = function setInstrumentationEnabled(enabled) { if (enabled && !this._instrumentationEnabled && this.editor) { HTMLInstrumentation.scanDocument(this.doc); HTMLInstrumentation._markText(this.editor); } this._instrumentationEnabled = enabled; }; /** * Returns true if document edits appear live in the connected browser * @return {boolean} */ HTMLDocument.prototype.isLiveEditingEnabled = function () { return this._instrumentationEnabled; }; /** * Returns a JSON object with HTTP response overrides * @param {boolean} enabled (Unused) * @return {{body: string}} */ HTMLDocument.prototype.getResponseData = function getResponseData(enabled) { var body; if (this._instrumentationEnabled) { if (this.editor) { body = HTMLInstrumentation.generateInstrumentedHTML(this.editor); } else { this.doc._ensureMasterEditor(); body = HTMLInstrumentation.generateInstrumentedHTML(this.doc._masterEditor); } } return { body: body || this.doc.getText() }; }; /** * Close the document */ HTMLDocument.prototype.close = function close() { if (this.editor) { this.editor.off(".HTMLDocument"); } if (this.doc) { this.doc.releaseRef(); } EditorManager.off("activeEditorChange", this._onActiveEditorChange); // Experimental code if (LiveDevelopment.config.experimental) { // Force highlight teardown this._onHighlight(); } }; /** * Attach new editor * @param {!Editor} editor The editor for this document */ HTMLDocument.prototype.attachToEditor = function (editor) { var self = this; this.editor = editor; // Performance optimization to use closures instead of Function.bind() // to improve responsiveness during cursor movement and keyboard events this.editor.on("cursorActivity.HTMLDocument", function (event, editor) { self._onCursorActivity(event, editor); }); this.editor.on("change.HTMLDocument", function (event, editor, change) { self._onChange(event, editor, change); }); this.editor.on("beforeDestroy.HTMLDocument", function (event, editor) { self._onDestroy(event, editor); }); // Experimental code if (LiveDevelopment.config.experimental) { HighlightAgent.on("highlight.HTMLDocument", function (event, node) { self._onHighlight(event, node); }); } if (this._instrumentationEnabled) { // Resync instrumentation with editor HTMLInstrumentation._markText(this.editor); } }; /** * Detach current editor */ HTMLDocument.prototype.detachFromEditor = function () { if (this.editor) { HighlightAgent.hide(); this.editor.off(".HTMLDocument"); this._removeHighlight(); this.editor = null; } }; /** * Update the highlight */ HTMLDocument.prototype.updateHighlight = function () { var editor = this.editor, ids = []; if (Inspector.config.highlight) { if (editor) { _.each(editor.getSelections(), function (sel) { var tagID = HTMLInstrumentation._getTagIDAtDocumentPos( editor, sel.reversed ? sel.end : sel.start ); if (tagID !== -1) { ids.push(tagID); } }); } if (!ids.length) { HighlightAgent.hide(); } else { HighlightAgent.domElement(ids); } } }; /** Event Handlers *******************************************************/ /** * Triggered on cursor activity by the editor * @param {$.Event} event Event * @param {!Editor} editor The editor for this document */ HTMLDocument.prototype._onCursorActivity = function (event, editor) { if (this.editor !== editor) { return; } this.updateHighlight(); }; /** * @private * For the given editor change, compare the resulting browser DOM with the * in-editor DOM. If there are any diffs, a warning is logged to the * console along with each diff. * @param {Object} change CodeMirror editor change data */ HTMLDocument.prototype._compareWithBrowser = function (change) { var self = this; RemoteAgent.call("getSimpleDOM").done(function (res) { var browserSimpleDOM = JSON.parse(res.result.value), edits, node, result; try { result = HTMLInstrumentation._getBrowserDiff(self.editor, browserSimpleDOM); } catch (err) { console.error("Error comparing in-browser DOM to in-editor DOM"); console.error(err.stack); return; } edits = result.diff.filter(function (delta) { // ignore textDelete in html root element node = result.browser.nodeMap[delta.parentID]; if (node && node.tag === "html" && delta.type === "textDelete") { return false; } return true; }); if (edits.length > 0) { console.warn("Browser DOM does not match after change: " + JSON.stringify(change)); edits.forEach(function (delta) { console.log(delta); }); } }); }; /** * Triggered when the editor is being destroyed * @param {$.Event} event Event * @param {!Editor} editor The editor being destroyed */ HTMLDocument.prototype._onDestroy = function (event, editor) { if (this.editor === editor) { this.detachFromEditor(); } }; /** * Triggered on change by the editor * @param {$.Event} event Event * @param {!Editor} editor The editor for this document * @param {Object} change CodeMirror editor change data */ HTMLDocument.prototype._onChange = function (event, editor, change) { // Make sure LiveHTML is turned on if (!this._instrumentationEnabled) { return; } // Apply DOM edits is async, so previous PerfUtils timer may still be // running. PerfUtils does not support running multiple timers with same // name, so do not start another timer in this case. var perfTimerName = "HTMLDocument applyDOMEdits", isNestedTimer = PerfUtils.isActive(perfTimerName); if (!isNestedTimer) { PerfUtils.markStart(perfTimerName); } // Only handles attribute changes currently. // TODO: text changes should be easy to add // TODO: if new tags are added, need to instrument them var self = this, result = HTMLInstrumentation.getUnappliedEditList(editor, change), applyEditsPromise; if (result.edits) { applyEditsPromise = RemoteAgent.call("applyDOMEdits", result.edits); applyEditsPromise.always(function () { if (!isNestedTimer) { PerfUtils.addMeasurement(perfTimerName); } }); } this.errors = result.errors || []; this.trigger("statusChanged", this); // Debug-only: compare in-memory vs. in-browser DOM // edit this file or set a conditional breakpoint at the top of this function: // "this._debug = true, false" if (this._debug) { console.log("Edits applied to browser were:"); console.log(JSON.stringify(result.edits, null, 2)); applyEditsPromise.done(function () { self._compareWithBrowser(change); }); } // var marker = HTMLInstrumentation._getMarkerAtDocumentPos( // this.editor, // editor.getCursorPos() // ); // // if (marker && marker.tagID) { // var range = marker.find(), // text = marker.doc.getRange(range.from, range.to); // // // HACK maintain ID // text = text.replace(">", " data-brackets-id='" + marker.tagID + "'>"); // // // FIXME incorrectly replaces body elements with content only, missing body element // RemoteAgent.remoteElement(marker.tagID).replaceWith(text); // } // if (!this.editor) { // return; // } // var codeMirror = this.editor._codeMirror; // while (change) { // var from = codeMirror.indexFromPos(change.from); // var to = codeMirror.indexFromPos(change.to); // var text = change.text.join("\n"); // DOMAgent.applyChange(from, to, text); // change = change.next; // } }; /** * Triggered when the active editor changes * @param {$.Event} event Event * @param {!Editor} newActive The new active editor * @param {!Editor} oldActive The old active editor */ HTMLDocument.prototype._onActiveEditorChange = function (event, newActive, oldActive) { this.detachFromEditor(); if (newActive && newActive.document === this.doc) { this.attachToEditor(newActive); } }; /** * Triggered by the HighlightAgent to highlight a node in the editor * @param {$.Event} event Event * @param {DOMElement} node Element to highlight */ HTMLDocument.prototype._onHighlight = function (event, node) { this._removeHighlight(); if (!node || !node.location || !this.editor) { return; } var codeMirror = this.editor._codeMirror; var to, from = codeMirror.posFromIndex(node.location); if (node.closeLocation) { to = node.closeLocation + node.closeLength; } else { to = node.location + node.length; } to = codeMirror.posFromIndex(to); this._highlight = codeMirror.markText(from, to, { className: "highlight" }); }; /** * Remove all highlighting */ HTMLDocument.prototype._removeHighlight = function () { if (this._highlight) { this._highlight.clear(); this._highlight = null; } }; // Export the class module.exports = HTMLDocument; }); ================================================ FILE: src/LiveDevelopment/Documents/JSDocument.js ================================================ /* * Copyright (c) 2012 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /*jslint forin: true */ /** * JSDocument manages a single JavaScript source document * * __EDITING__ * * Editing the document will cause the script to be reloaded via the * ScriptAgent, which updates the implementation of all functions without * loosing any state. To support redrawing canvases, jQuery must be loaded * and a rerender method must be attached to every canvas that clears and * renders the canvas. * * __HIGHLIGHTING__ * * JSDocument supports highlighting nodes from the HighlightAgent. Support * for highlighting the nodes that were created / touched by the current * line is missing. */ define(function JSDocumentModule(require, exports, module) { "use strict"; var EventDispatcher = require("utils/EventDispatcher"), Inspector = require("LiveDevelopment/Inspector/Inspector"), ScriptAgent = require("LiveDevelopment/Agents/ScriptAgent"), HighlightAgent = require("LiveDevelopment/Agents/HighlightAgent"); /** * @constructor * @param {!Document} doc The source document from Brackets * @param {!Editor} editor The editor for this document */ var JSDocument = function JSDocument(doc, editor) { if (!editor) { return; } this.doc = doc; this.editor = editor; this.onHighlight = this.onHighlight.bind(this); this.onChange = this.onChange.bind(this); this.onCursorActivity = this.onCursorActivity.bind(this); HighlightAgent.on("highlight", this.onHighlight); this.editor.on("change", this.onChange); this.editor.on("cursorActivity", this.onCursorActivity); this.onCursorActivity(); }; // JSDocument doesn't dispatch events, but the "live document" interface requires having an on() API EventDispatcher.makeEventDispatcher(JSDocument.prototype); /** Close the document */ JSDocument.prototype.close = function close() { if (!this.editor) { return; } HighlightAgent.off("highlight", this.onHighlight); this.editor.off("change", this.onChange); this.editor.off("cursorActivity", this.onCursorActivity); this.onHighlight(); }; JSDocument.prototype.script = function script() { return ScriptAgent.scriptForURL(this.doc.url); }; /** Event Handlers *******************************************************/ /** Triggered on cursor activity by the editor */ JSDocument.prototype.onCursorActivity = function onCursorActivity(event, editor) { }; /** Triggered on change by the editor */ JSDocument.prototype.onChange = function onChange(event, editor, change) { var src = this.doc.getText(); Inspector.Debugger.setScriptSource(this.script().scriptId, src, function onSetScriptSource(res) { Inspector.Runtime.evaluate("if($)$(\"canvas\").each(function(i,e){if(e.rerender)e.rerender()})"); }.bind(this)); }; /** Triggered by the HighlightAgent to highlight a node in the editor */ JSDocument.prototype.onHighlight = function onHighlight(event, node) { // clear an existing highlight var codeMirror = this.editor._codeMirror; var i; for (i in this._highlight) { codeMirror.removeLineClass(this._highlight[i], "wrap", "highlight"); } this._highlight = []; if (!node || !node.trace) { return; } // go through the trace and find highlight the lines of this script var scriptId = this.script().scriptId; var callFrame, line; for (i in node.trace) { callFrame = node.trace[i]; if (callFrame.location && callFrame.location.scriptId === scriptId) { line = callFrame.location.lineNumber; codeMirror.addLineClass(line, "wrap", "highlight"); this._highlight.push(line); } } }; // Export the class module.exports = JSDocument; }); ================================================ FILE: src/LiveDevelopment/Inspector/Inspector.js ================================================ /* * Copyright (c) 2012 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /*jslint forin: true */ /*global FileError */ /** * Inspector manages the connection to Chrome/Chromium's remote debugger. * See inspector.html for the documentation of the remote debugger. * * __SETUP__ * * To enable remote debugging in Chrome or Chromium open either application * with the following parameters: * * --enable-remote-debugger --remote-debugging-port=9222 * * This will open an HTTP server on the specified port, which can be used to * browse the available remote debugger sessions. In general, every open * browser tab can host an individual remote debugger session. The * available interfaces can be exported by requesting: * * http://127.0.0.1:9222/json * * The response is a JSON-formatted array that specifies all available remote * debugger sessions including the remote debugging web sockets. * * Inspector can connect directly to a web socket via `connect(socketURL)`, or * it can find the web socket that corresponds to the tab at the given URL and * connect to it via `connectToURL(url)`. The later returns a promise. To * disconnect use `disconnect()`. * * __EVENTS__ * * Inspector dispatches several connectivity-related events + all remote debugger * events (see below). Event handlers are attached via `on(event, function)` and * detached via `off(event, function)`. * * `connect` Inspector did successfully connect to the remote debugger * `disconnect` Inspector did disconnect from the remote debugger * `error` Inspector encountered an error * `message` Inspector received a message from the remote debugger - this * provides a low-level entry point to remote debugger events * * __REMOTE DEBUGGER COMMANDS__ * * Commands are executed by calling `{Domain}.{Command}()` with the parameters * specified in the order of the remote debugger documentation. These command * functions are generated automatically at runtime from Inspector.json. The * actual implementation of these functions is found in * `_send(method, signature, varargs)`, which verifies, serializes, and * transmits the command to the remote debugger. If the last parameter of any * command function call is a function, it will be used as the callback. * * __REMOTE DEBUGGER EVENTS__ * * Debugger events are dispatched as regular events using {Domain}.{Event} as * the event name. The handler function will be called with a single parameter * that stores all returned values as an object. */ define(function Inspector(require, exports, module) { "use strict"; var Async = require("utils/Async"), EventDispatcher = require("utils/EventDispatcher"); /** * Map message IDs to the callback function and original JSON message * @type {Object. value} if (signature) { for (i in signature) { if (_verifySignature(args[i], signature[i])) { params[signature[i].name] = args[i]; } } } // Store message callback and send message msg = { method: method, id: id, params: params }; _messageCallbacks[id] = { callback: callback, message: msg }; _socket.send(JSON.stringify(msg)); return promise; } /** * Manually send a message to the remote debugger * All passed arguments after the command are passed on as parameters. * If the last argument is a function, it is used as the callback function. * @param {string} domain * @param {string} command */ function send(domain, command, varargs) { return _send(domain + "." + command, null, varargs); } /** WebSocket did close */ function _onDisconnect() { _socket = undefined; exports.trigger("disconnect"); } /** WebSocket reported an error */ function _onError(error) { if (_connectDeferred) { _connectDeferred.reject(); _connectDeferred = null; } exports.trigger("error", error); } /** WebSocket did open */ function _onConnect() { if (_connectDeferred) { _connectDeferred.resolve(); _connectDeferred = null; } exports.trigger("connect"); } /** Received message from the WebSocket * A message can be one of three things: * 1. an error -> report it * 2. the response to a previous command -> run the stored callback * 3. an event -> trigger an event handler method * @param {object} message */ function _onMessage(message) { var response = JSON.parse(message.data), msgRecord = _messageCallbacks[response.id], callback = msgRecord && msgRecord.callback, msgText = (msgRecord && msgRecord.message) || "No message"; if (msgRecord) { // Messages with an ID are a response to a command, fire callback callback(response.result, response.error); delete _messageCallbacks[response.id]; } else if (response.method) { // Messages with a method are an event, trigger event handlers var domainAndMethod = response.method.split("."), domain = domainAndMethod[0], method = domainAndMethod[1]; EventDispatcher.triggerWithArray(exports[domain], method, response.params); } // Always fire event handlers for all messages/errors exports.trigger("message", response); if (response.error) { exports.trigger("error", response.error, msgText); } } /** Public Functions *****************************************************/ /** Get a list of the available windows/tabs/extensions that are remote-debuggable * @param {string} host IP or name * @param {integer} debugger port */ function getDebuggableWindows(host, port) { if (!host) { host = "127.0.0.1"; } if (!port) { port = 9222; } var def = new $.Deferred(); var request = new XMLHttpRequest(); request.open("GET", "http://" + host + ":" + port + "/json"); request.onload = function onLoad() { var sockets = JSON.parse(request.response); def.resolve(sockets); }; request.onerror = function onError() { def.reject(request.response); }; request.send(null); return def.promise(); } /** * Disconnect from the remote debugger WebSocket * @return {jQuery.Promise} Promise that is resolved immediately if not * currently connected or asynchronously when the socket is closed. */ function disconnect() { var deferred = new $.Deferred(), promise = deferred.promise(); if (_socket && (_socket.readyState === WebSocket.OPEN)) { _socket.onclose = function () { // trigger disconnect event _onDisconnect(); deferred.resolve(); }; promise = Async.withTimeout(promise, 5000); _socket.close(); } else { if (_socket) { delete _socket.onmessage; delete _socket.onopen; delete _socket.onclose; delete _socket.onerror; _socket = undefined; } deferred.resolve(); } return promise; } /** * Connect to the remote debugger WebSocket at the given URL. * Clients must listen for the `connect` event. * @param {string} WebSocket URL */ function connect(socketURL) { disconnect().done(function () { _socket = new WebSocket(socketURL); _socket.onmessage = _onMessage; _socket.onopen = _onConnect; _socket.onclose = _onDisconnect; _socket.onerror = _onError; }); } /** Connect to the remote debugger of the page that is at the given URL * @param {string} url */ function connectToURL(url) { if (_connectDeferred) { // reject an existing connection attempt _connectDeferred.reject("CANCEL"); } var deferred = new $.Deferred(); _connectDeferred = deferred; var promise = getDebuggableWindows(); promise.done(function onGetAvailableSockets(response) { var i, page; for (i in response) { page = response[i]; if (page.webSocketDebuggerUrl && page.url.indexOf(url) === 0) { connect(page.webSocketDebuggerUrl); // _connectDeferred may be resolved by onConnect or rejected by onError return; } } deferred.reject(FileError.ERR_NOT_FOUND); // Reject with a "not found" error }); promise.fail(function onFail(err) { deferred.reject(err); }); return deferred.promise(); } /** Check if the inspector is connected */ function connected() { return _socket !== undefined && _socket.readyState === WebSocket.OPEN; } /** * Get user agent string * @return {string} */ function getUserAgent() { return _userAgent; } /** * Set user agent string * @param {string} userAgent User agent string returned from Chrome */ function setUserAgent(userAgent) { _userAgent = userAgent; } /** Initialize the Inspector * Read the Inspector.json configuration and define the command objects * -> Inspector.domain.command() */ function init(theConfig) { exports.config = theConfig; var InspectorText = require("text!LiveDevelopment/Inspector/Inspector.json"), InspectorJSON = JSON.parse(InspectorText); var i, j, domain, command; for (i in InspectorJSON.domains) { domain = InspectorJSON.domains[i]; var exportedDomain = {}; exports[domain.domain] = exportedDomain; EventDispatcher.makeEventDispatcher(exportedDomain); for (j in domain.commands) { command = domain.commands[j]; exportedDomain[command.name] = _send.bind(undefined, domain.domain + "." + command.name, command.parameters); } } } EventDispatcher.makeEventDispatcher(exports); // Export public functions exports.connect = connect; exports.connected = connected; exports.connectToURL = connectToURL; exports.disconnect = disconnect; exports.getDebuggableWindows = getDebuggableWindows; exports.getUserAgent = getUserAgent; exports.init = init; exports.send = send; exports.setUserAgent = setUserAgent; }); ================================================ FILE: src/LiveDevelopment/Inspector/Inspector.json ================================================ { "version": { "major": "1", "minor": "1" }, "domains": [{ "domain": "Inspector", "hidden": true, "types": [], "commands": [ { "name": "enable", "description": "Enables inspector domain notifications." }, { "name": "disable", "description": "Disables inspector domain notifications." }, { "name": "reset", "description": "Resets all domains." } ], "events": [ { "name": "evaluateForTestInFrontend", "parameters": [ { "name": "testCallId", "type": "integer" }, { "name": "script", "type": "string" } ] }, { "name": "inspect", "parameters": [ { "name": "object", "$ref": "Runtime.RemoteObject" }, { "name": "hints", "type": "object" } ] }, { "name": "detached", "description": "Fired when remote debugging connection is about to be terminated. Contains detach reason.", "parameters": [ { "name": "reason", "type": "string", "description": "The reason why connection has been terminated." } ] }, { "name": "targetCrashed", "description": "Fired when debugging target has crashed" } ] }, { "domain": "Memory", "hidden": true, "types": [ { "id": "MemoryBlock", "type": "object", "properties": [ { "name": "size", "type": "number", "optional": true, "description": "Size of the block in bytes if available" }, { "name": "name", "type": "string", "description": "Unique name used to identify the component that allocated this block" }, { "name": "children", "type": "array", "optional": true, "items": { "$ref": "MemoryBlock" }} ] }, { "id": "HeapSnapshotChunk", "type": "object", "properties": [ { "name": "strings", "type": "array", "items": { "type": "string" }, "description": "An array of strings that were found since last update." }, { "name": "nodes", "type": "array", "items": { "type": "integer" }, "description": "An array of nodes that were found since last update." }, { "name": "edges", "type": "array", "items": { "type": "integer" }, "description": "An array of edges that were found since last update." }, { "name": "baseToRealNodeId", "type": "array", "items": { "type": "integer" }, "description": "An array of integers for nodeId remapping. Even nodeId has to be mapped to the following odd nodeId." } ] } ], "commands": [ { "name": "getDOMCounters", "returns": [ { "name": "documents", "type": "integer" }, { "name": "nodes", "type": "integer" }, { "name": "jsEventListeners", "type": "integer" } ] } ], "events": [ { "name": "addNativeSnapshotChunk", "parameters": [ { "name": "chunk", "$ref": "HeapSnapshotChunk", "description": "A chunk of the serialized the snapshot." } ] } ] }, { "domain": "Page", "description": "Actions and events related to the inspected page belong to the page domain.", "types": [ { "id": "ResourceType", "type": "string", "enum": ["Document", "Stylesheet", "Image", "Font", "Script", "XHR", "WebSocket", "Other"], "description": "Resource type as it was perceived by the rendering engine." }, { "id": "FrameId", "type": "string", "description": "Unique frame identifier." }, { "id": "Frame", "type": "object", "description": "Information about the Frame on the page.", "properties": [ { "name": "id", "type": "string", "description": "Frame unique identifier." }, { "name": "parentId", "type": "string", "optional": true, "description": "Parent frame identifier." }, { "name": "loaderId", "$ref": "Network.LoaderId", "description": "Identifier of the loader associated with this frame." }, { "name": "name", "type": "string", "optional": true, "description": "Frame's name as specified in the tag." }, { "name": "url", "type": "string", "description": "Frame document's URL." }, { "name": "securityOrigin", "type": "string", "description": "Frame document's security origin." }, { "name": "mimeType", "type": "string", "description": "Frame document's mimeType as determined by the browser." } ] }, { "id": "FrameResourceTree", "type": "object", "description": "Information about the Frame hierarchy along with their cached resources.", "properties": [ { "name": "frame", "$ref": "Frame", "description": "Frame information for this tree item." }, { "name": "childFrames", "type": "array", "optional": true, "items": { "$ref": "FrameResourceTree" }, "description": "Child frames." }, { "name": "resources", "type": "array", "items": { "type": "object", "properties": [ { "name": "url", "type": "string", "description": "Resource URL." }, { "name": "type", "$ref": "ResourceType", "description": "Type of this resource." }, { "name": "mimeType", "type": "string", "description": "Resource mimeType as determined by the browser." }, { "name": "failed", "type": "boolean", "optional": true, "description": "True if the resource failed to load." }, { "name": "canceled", "type": "boolean", "optional": true, "description": "True if the resource was canceled during loading." } ] }, "description": "Information about frame resources." } ], "hidden": true }, { "id": "SearchMatch", "type": "object", "description": "Search match for resource.", "properties": [ { "name": "lineNumber", "type": "number", "description": "Line number in resource content." }, { "name": "lineContent", "type": "string", "description": "Line with match content." } ], "hidden": true }, { "id": "SearchResult", "type": "object", "description": "Search result for resource.", "properties": [ { "name": "url", "type": "string", "description": "Resource URL." }, { "name": "frameId", "$ref": "FrameId", "description": "Resource frame id." }, { "name": "matchesCount", "type": "number", "description": "Number of matches in the resource content." } ], "hidden": true }, { "id": "Cookie", "type": "object", "description": "Cookie object", "properties": [ { "name": "name", "type": "string", "description": "Cookie name." }, { "name": "value", "type": "string", "description": "Cookie value." }, { "name": "domain", "type": "string", "description": "Cookie domain." }, { "name": "path", "type": "string", "description": "Cookie path." }, { "name": "expires", "type": "number", "description": "Cookie expires." }, { "name": "size", "type": "integer", "description": "Cookie size." }, { "name": "httpOnly", "type": "boolean", "description": "True if cookie is http-only." }, { "name": "secure", "type": "boolean", "description": "True if cookie is secure." }, { "name": "session", "type": "boolean", "description": "True in case of session cookie." } ], "hidden": true }, { "id": "ScriptIdentifier", "type": "string", "description": "Unique script identifier.", "hidden": true }, { "id": "NavigationEntry", "type": "object", "description": "Navigation history entry.", "properties": [ { "name": "id", "type": "integer", "description": "Unique id of the navigation history entry." }, { "name": "url", "type": "string", "description": "URL of the navigation history entry." }, { "name": "title", "type": "string", "description": "Title of the navigation history entry." } ], "hidden": true } ], "commands": [ { "name": "enable", "description": "Enables page domain notifications." }, { "name": "disable", "description": "Disables page domain notifications." }, { "name": "addScriptToEvaluateOnLoad", "parameters": [ { "name": "scriptSource", "type": "string" } ], "returns": [ { "name": "identifier", "$ref": "ScriptIdentifier", "description": "Identifier of the added script." } ], "hidden": true }, { "name": "removeScriptToEvaluateOnLoad", "parameters": [ { "name": "identifier", "$ref": "ScriptIdentifier" } ], "hidden": true }, { "name": "reload", "parameters": [ { "name": "ignoreCache", "type": "boolean", "optional": true, "description": "If true, browser cache is ignored (as if the user pressed Shift+refresh)." }, { "name": "scriptToEvaluateOnLoad", "type": "string", "optional": true, "description": "If set, the script will be injected into all frames of the inspected page after reload." }, { "name": "scriptPreprocessor", "type": "string", "optional": true, "description": "Script body that should evaluate to function that will preprocess all the scripts before their compilation.", "hidden": true } ], "description": "Reloads given page optionally ignoring the cache." }, { "name": "navigate", "parameters": [ { "name": "url", "type": "string", "description": "URL to navigate the page to." } ], "description": "Navigates current page to the given URL." }, { "name": "getNavigationHistory", "parameters": [], "returns": [ { "name": "currentIndex", "type": "integer", "description": "Index of the current navigation history entry." }, { "name": "entries", "type": "array", "items": { "$ref": "NavigationEntry"}, "description": "Array of navigation history entries." } ], "description": "Returns navigation history for the current page.", "hidden": true }, { "name": "navigateToHistoryEntry", "parameters": [ { "name": "entryId", "type": "integer", "description": "Unique id of the entry to navigate to." } ], "description": "Navigates current page to the given history entry.", "hidden": true }, { "name": "getCookies", "returns": [ { "name": "cookies", "type": "array", "items": { "$ref": "Cookie"}, "description": "Array of cookie objects." }, { "name": "cookiesString", "type": "string", "description": "document.cookie string representation of the cookies." } ], "description": "Returns all browser cookies. Depending on the backend support, will either return detailed cookie information in the cookie field or string cookie representation using cookieString.", "hidden": true }, { "name": "deleteCookie", "parameters": [ { "name": "cookieName", "type": "string", "description": "Name of the cookie to remove." }, { "name": "url", "type": "string", "description": "URL to match cooke domain and path." } ], "description": "Deletes browser cookie with given name, domain and path.", "hidden": true }, { "name": "getResourceTree", "description": "Returns present frame / resource tree structure.", "returns": [ { "name": "frameTree", "$ref": "FrameResourceTree", "description": "Present frame / resource tree structure." } ], "hidden": true }, { "name": "getResourceContent", "description": "Returns content of the given resource.", "parameters": [ { "name": "frameId", "$ref": "FrameId", "description": "Frame id to get resource for." }, { "name": "url", "type": "string", "description": "URL of the resource to get content for." } ], "returns": [ { "name": "content", "type": "string", "description": "Resource content." }, { "name": "base64Encoded", "type": "boolean", "description": "True, if content was served as base64." } ], "hidden": true }, { "name": "searchInResource", "description": "Searches for given string in resource content.", "parameters": [ { "name": "frameId", "$ref": "FrameId", "description": "Frame id for resource to search in." }, { "name": "url", "type": "string", "description": "URL of the resource to search in." }, { "name": "query", "type": "string", "description": "String to search for." }, { "name": "caseSensitive", "type": "boolean", "optional": true, "description": "If true, search is case sensitive." }, { "name": "isRegex", "type": "boolean", "optional": true, "description": "If true, treats string parameter as regex." } ], "returns": [ { "name": "result", "type": "array", "items": { "$ref": "SearchMatch" }, "description": "List of search matches." } ], "hidden": true }, { "name": "searchInResources", "description": "Searches for given string in frame / resource tree structure.", "parameters": [ { "name": "text", "type": "string", "description": "String to search for." }, { "name": "caseSensitive", "type": "boolean", "optional": true, "description": "If true, search is case sensitive." }, { "name": "isRegex", "type": "boolean", "optional": true, "description": "If true, treats string parameter as regex." } ], "returns": [ { "name": "result", "type": "array", "items": { "$ref": "SearchResult" }, "description": "List of search results." } ], "hidden": true }, { "name": "setDocumentContent", "description": "Sets given markup as the document's HTML.", "parameters": [ { "name": "frameId", "$ref": "FrameId", "description": "Frame id to set HTML for." }, { "name": "html", "type": "string", "description": "HTML content to set." } ], "hidden": true }, { "name": "setDeviceMetricsOverride", "description": "Overrides the values of device screen dimensions (window.screen.width, window.screen.height, window.innerWidth, window.innerHeight, and \"device-width\"/\"device-height\"-related CSS media query results) and the font scale factor.", "parameters": [ { "name": "width", "type": "integer", "description": "Overriding width value in pixels (minimum 0, maximum 10000000). 0 disables the override." }, { "name": "height", "type": "integer", "description": "Overriding height value in pixels (minimum 0, maximum 10000000). 0 disables the override." }, { "name": "fontScaleFactor", "type": "number", "description": "Overriding font scale factor value (must be positive). 1 disables the override." }, { "name": "fitWindow", "type": "boolean", "description": "Whether a view that exceeds the available browser window area should be scaled down to fit." } ], "hidden": true }, { "name": "setShowPaintRects", "description": "Requests that backend shows paint rectangles", "parameters": [ { "name": "result", "type": "boolean", "description": "True for showing paint rectangles" } ], "hidden": true }, { "name": "setShowDebugBorders", "description": "Requests that backend shows debug borders on layers", "parameters": [ { "name": "show", "type": "boolean", "description": "True for showing debug borders" } ], "hidden": true }, { "name": "setShowFPSCounter", "description": "Requests that backend shows the FPS counter", "parameters": [ { "name": "show", "type": "boolean", "description": "True for showing the FPS counter" } ], "hidden": true }, { "name": "setContinuousPaintingEnabled", "description": "Requests that backend enables continuous painting", "parameters": [ { "name": "enabled", "type": "boolean", "description": "True for enabling cointinuous painting" } ], "hidden": true }, { "name": "setShowScrollBottleneckRects", "description": "Requests that backend shows scroll bottleneck rects", "parameters": [ { "name": "show", "type": "boolean", "description": "True for showing scroll bottleneck rects" } ], "hidden": true }, { "name": "getScriptExecutionStatus", "description": "Determines if scripts can be executed in the page.", "returns": [ { "name": "result", "type": "string", "enum": ["allowed", "disabled", "forbidden"], "description": "Script execution status: \"allowed\" if scripts can be executed, \"disabled\" if script execution has been disabled through page settings, \"forbidden\" if script execution for the given page is not possible for other reasons." } ], "hidden": true }, { "name": "setScriptExecutionDisabled", "description": "Switches script execution in the page.", "parameters": [ { "name": "value", "type": "boolean", "description": "Whether script execution should be disabled in the page." } ], "hidden": true }, { "name": "setGeolocationOverride", "description": "Overrides the Geolocation Position or Error.", "parameters": [ { "name": "latitude", "type": "number", "optional": true, "description": "Mock longitude"}, { "name": "longitude", "type": "number", "optional": true, "description": "Mock latitude"}, { "name": "accuracy", "type": "number", "optional": true, "description": "Mock accuracy"} ] }, { "name": "clearGeolocationOverride", "description": "Clears the overriden Geolocation Position and Error." }, { "name": "setDeviceOrientationOverride", "description": "Overrides the Device Orientation.", "parameters": [ { "name": "alpha", "type": "number", "description": "Mock alpha"}, { "name": "beta", "type": "number", "description": "Mock beta"}, { "name": "gamma", "type": "number", "description": "Mock gamma"} ], "hidden": true }, { "name": "clearDeviceOrientationOverride", "description": "Clears the overridden Device Orientation.", "hidden": true }, { "name": "setTouchEmulationEnabled", "parameters": [ { "name": "enabled", "type": "boolean", "description": "Whether the touch event emulation should be enabled." } ], "description": "Toggles mouse event-based touch event emulation.", "hidden": true }, { "name": "setEmulatedMedia", "parameters": [ { "name": "media", "type": "string", "description": "Media type to emulate. Empty string disables the override." } ], "description": "Emulates the given media for CSS media queries.", "hidden": true }, { "name": "captureScreenshot", "description": "Capture page screenshot.", "parameters": [ { "name": "format", "type": "string", "optional": true, "enum": ["jpeg", "png"], "description": "Image compression format." }, { "name": "quality", "type": "integer", "optional": true, "description": "Compression quality from range [0..100]." }, { "name": "maxWidth", "type": "integer", "optional": true, "description": "Maximum screenshot width." }, { "name": "maxHeight", "type": "integer", "optional": true, "description": "Maximum screenshot height." } ], "returns": [ { "name": "data", "type": "string", "description": "Base64-encoded image data (PNG)." }, { "name": "deviceScaleFactor", "type": "number", "hidden": true, "description": "Device scale factor." }, { "name": "pageScaleFactor", "type": "number", "hidden": true, "description": "Page scale factor." }, { "name": "viewport", "$ref": "DOM.Rect", "hidden": true, "description": "Viewport in CSS pixels." } ] }, { "name": "startScreencast", "description": "Starts sending each frame using the screencastFrame event.", "parameters": [ { "name": "format", "type": "string", "optional": true, "enum": ["jpeg", "png"], "description": "Image compression format." }, { "name": "quality", "type": "integer", "optional": true, "description": "Compression quality from range [0..100]." }, { "name": "maxWidth", "type": "integer", "optional": true, "description": "Maximum screenshot width." }, { "name": "maxHeight", "type": "integer", "optional": true, "description": "Maximum screenshot height." } ], "hidden": true }, { "name": "stopScreencast", "description": "Stops sending each frame in the screencastFrame.", "hidden": true }, { "name": "handleJavaScriptDialog", "description": "Accepts or dismisses a JavaScript initiated dialog (alert, confirm, prompt, or onbeforeunload).", "parameters": [ { "name": "accept", "type": "boolean", "description": "Whether to accept or dismiss the dialog." }, { "name": "promptText", "type": "string", "optional": true, "description": "The text to enter into the dialog prompt before accepting. Used only if this is a prompt dialog." } ], "hidden": true }, { "name": "setShowViewportSizeOnResize", "description": "Paints viewport size upon main frame resize.", "parameters": [ { "name": "show", "type": "boolean", "description": "Whether to paint size or not." }, { "name": "showGrid", "type": "boolean", "optional": true, "description": "Whether to paint grid as well." } ], "hidden": true }, { "name": "setForceCompositingMode", "description": "Force accelerated compositing mode for inspected page.", "parameters": [ { "name": "force", "type": "boolean", "description": "Whether to force accelerated compositing or not." } ], "hidden": true } ], "events": [ { "name": "domContentEventFired", "parameters": [ { "name": "timestamp", "type": "number" } ] }, { "name": "loadEventFired", "parameters": [ { "name": "timestamp", "type": "number" } ] }, { "name": "frameAttached", "description": "Fired when frame has been attached to its parent.", "parameters": [ { "name": "frameId", "$ref": "FrameId", "description": "Id of the frame that has been attached." } ] }, { "name": "frameNavigated", "description": "Fired once navigation of the frame has completed. Frame is now associated with the new loader.", "parameters": [ { "name": "frame", "$ref": "Frame", "description": "Frame object." } ] }, { "name": "frameDetached", "description": "Fired when frame has been detached from its parent.", "parameters": [ { "name": "frameId", "$ref": "FrameId", "description": "Id of the frame that has been detached." } ] }, { "name": "frameStartedLoading", "description": "Fired when frame has started loading.", "parameters": [ { "name": "frameId", "$ref": "FrameId", "description": "Id of the frame that has started loading." } ], "hidden": true }, { "name": "frameStoppedLoading", "description": "Fired when frame has stopped loading.", "parameters": [ { "name": "frameId", "$ref": "FrameId", "description": "Id of the frame that has stopped loading." } ], "hidden": true }, { "name": "frameScheduledNavigation", "description": "Fired when frame schedules a potential navigation.", "parameters": [ { "name": "frameId", "$ref": "FrameId", "description": "Id of the frame that has scheduled a navigation." }, { "name": "delay", "type": "number", "description": "Delay (in seconds) until the navigation is scheduled to begin. The navigation is not guaranteed to start." } ], "hidden": true }, { "name": "frameClearedScheduledNavigation", "description": "Fired when frame no longer has a scheduled navigation.", "parameters": [ { "name": "frameId", "$ref": "FrameId", "description": "Id of the frame that has cleared its scheduled navigation." } ], "hidden": true }, { "name": "javascriptDialogOpening", "description": "Fired when a JavaScript initiated dialog (alert, confirm, prompt, or onbeforeunload) is about to open.", "parameters": [ { "name": "message", "type": "string", "description": "Message that will be displayed by the dialog." } ], "hidden": true }, { "name": "javascriptDialogClosed", "description": "Fired when a JavaScript initiated dialog (alert, confirm, prompt, or onbeforeunload) has been closed.", "hidden": true }, { "name": "scriptsEnabled", "description": "Fired when the JavaScript is enabled/disabled on the page", "parameters": [ { "name": "isEnabled", "type": "boolean", "description": "Whether script execution is enabled or disabled on the page." } ], "hidden": true }, { "name": "screencastFrame", "description": "Compressed image data requested by the startScreencast.", "parameters": [ { "name": "data", "type": "string", "description": "Base64-encoded compressed image." }, { "name": "deviceScaleFactor", "type": "number", "hidden": true, "optional": true, "description": "Device scale factor." }, { "name": "pageScaleFactor", "type": "number", "hidden": true, "optional": true, "description": "Page scale factor." }, { "name": "viewport", "$ref": "DOM.Rect", "hidden": true, "optional": true, "description": "Viewport in CSS pixels." }, { "name": "offsetTop", "type": "number", "hidden": true, "optional": true, "description": "Top offset in DIP." }, { "name": "offsetBottom", "type": "number", "hidden": true, "optional": true, "description": "Bottom offset in DIP." } ], "hidden": true }, { "name": "screencastVisibilityChanged", "description": "Fired when the page with currently enabled screencast was shown or hidden .", "parameters": [ { "name": "visible", "type": "boolean", "description": "True if the page is visible." } ], "hidden": true } ] }, { "domain": "Runtime", "description": "Runtime domain exposes JavaScript runtime by means of remote evaluation and mirror objects. Evaluation results are returned as mirror object that expose object type, string representation and unique identifier that can be used for further object reference. Original objects are maintained in memory unless they are either explicitly released or are released along with the other objects in their object group.", "types": [ { "id": "RemoteObjectId", "type": "string", "description": "Unique object identifier." }, { "id": "RemoteObject", "type": "object", "description": "Mirror object referencing original JavaScript object.", "properties": [ { "name": "type", "type": "string", "enum": ["object", "function", "undefined", "string", "number", "boolean"], "description": "Object type." }, { "name": "subtype", "type": "string", "optional": true, "enum": ["array", "null", "node", "regexp", "date"], "description": "Object subtype hint. Specified for object type values only." }, { "name": "className", "type": "string", "optional": true, "description": "Object class (constructor) name. Specified for object type values only." }, { "name": "value", "type": "any", "optional": true, "description": "Remote object value (in case of primitive values or JSON values if it was requested)." }, { "name": "description", "type": "string", "optional": true, "description": "String representation of the object." }, { "name": "objectId", "$ref": "RemoteObjectId", "optional": true, "description": "Unique object identifier (for non-primitive values)." }, { "name": "preview", "$ref": "ObjectPreview", "optional": true, "description": "Preview containing abbreviated property values.", "hidden": true } ] }, { "id": "ObjectPreview", "type": "object", "hidden": true, "description": "Object containing abbreviated remote object value.", "properties": [ { "name": "lossless", "type": "boolean", "description": "Determines whether preview is lossless (contains all information of the original object)." }, { "name": "overflow", "type": "boolean", "description": "True iff some of the properties of the original did not fit." }, { "name": "properties", "type": "array", "items": { "$ref": "PropertyPreview" }, "description": "List of the properties." } ] }, { "id": "PropertyPreview", "type": "object", "hidden": true, "properties": [ { "name": "name", "type": "string", "description": "Property name." }, { "name": "type", "type": "string", "enum": ["object", "function", "undefined", "string", "number", "boolean"], "description": "Object type." }, { "name": "value", "type": "string", "optional": true, "description": "User-friendly property value string." }, { "name": "valuePreview", "$ref": "ObjectPreview", "optional": true, "description": "Nested value preview." }, { "name": "subtype", "type": "string", "optional": true, "enum": ["array", "null", "node", "regexp", "date"], "description": "Object subtype hint. Specified for object type values only." } ] }, { "id": "PropertyDescriptor", "type": "object", "description": "Object property descriptor.", "properties": [ { "name": "name", "type": "string", "description": "Property name." }, { "name": "value", "$ref": "RemoteObject", "optional": true, "description": "The value associated with the property." }, { "name": "writable", "type": "boolean", "optional": true, "description": "True if the value associated with the property may be changed (data descriptors only)." }, { "name": "get", "$ref": "RemoteObject", "optional": true, "description": "A function which serves as a getter for the property, or undefined if there is no getter (accessor descriptors only)." }, { "name": "set", "$ref": "RemoteObject", "optional": true, "description": "A function which serves as a setter for the property, or undefined if there is no setter (accessor descriptors only)." }, { "name": "configurable", "type": "boolean", "description": "True if the type of this property descriptor may be changed and if the property may be deleted from the corresponding object." }, { "name": "enumerable", "type": "boolean", "description": "True if this property shows up during enumeration of the properties on the corresponding object." }, { "name": "wasThrown", "type": "boolean", "optional": true, "description": "True if the result was thrown during the evaluation." }, { "name": "isOwn", "optional": true, "type": "boolean", "description": "True if the property is owned for the object.", "hidden": true } ] }, { "id": "InternalPropertyDescriptor", "type": "object", "description": "Object internal property descriptor. This property isn't normally visible in JavaScript code.", "properties": [ { "name": "name", "type": "string", "description": "Conventional property name." }, { "name": "value", "$ref": "RemoteObject", "optional": true, "description": "The value associated with the property." } ], "hidden": true }, { "id": "CallArgument", "type": "object", "description": "Represents function call argument. Either remote object id objectId or primitive value or neither of (for undefined) them should be specified.", "properties": [ { "name": "value", "type": "any", "optional": true, "description": "Primitive value." }, { "name": "objectId", "$ref": "RemoteObjectId", "optional": true, "description": "Remote object handle." } ] }, { "id": "ExecutionContextId", "type": "integer", "description": "Id of an execution context." }, { "id": "ExecutionContextDescription", "type": "object", "description": "Description of an isolated world.", "properties": [ { "name": "id", "$ref": "ExecutionContextId", "description": "Unique id of the execution context. It can be used to specify in which execution context script evaluation should be performed." }, { "name": "isPageContext", "type": "boolean", "description": "True if this is a context where inspected web page scripts run. False if it is a content script isolated context.", "hidden": true }, { "name": "name", "type": "string", "description": "Human readable name describing given context.", "hidden": true}, { "name": "frameId", "$ref": "Page.FrameId", "description": "Id of the owning frame." } ] } ], "commands": [ { "name": "evaluate", "parameters": [ { "name": "expression", "type": "string", "description": "Expression to evaluate." }, { "name": "objectGroup", "type": "string", "optional": true, "description": "Symbolic group name that can be used to release multiple objects." }, { "name": "includeCommandLineAPI", "type": "boolean", "optional": true, "description": "Determines whether Command Line API should be available during the evaluation.", "hidden": true }, { "name": "doNotPauseOnExceptionsAndMuteConsole", "type": "boolean", "optional": true, "description": "Specifies whether evaluation should stop on exceptions and mute console. Overrides setPauseOnException state.", "hidden": true }, { "name": "contextId", "$ref": "Runtime.ExecutionContextId", "optional": true, "description": "Specifies in which isolated context to perform evaluation. Each content script lives in an isolated context and this parameter may be used to specify one of those contexts. If the parameter is omitted or 0 the evaluation will be performed in the context of the inspected page." }, { "name": "returnByValue", "type": "boolean", "optional": true, "description": "Whether the result is expected to be a JSON object that should be sent by value." }, { "name": "generatePreview", "type": "boolean", "optional": true, "hidden": true, "description": "Whether preview should be generated for the result." } ], "returns": [ { "name": "result", "$ref": "RemoteObject", "description": "Evaluation result." }, { "name": "wasThrown", "type": "boolean", "optional": true, "description": "True if the result was thrown during the evaluation." } ], "description": "Evaluates expression on global object." }, { "name": "callFunctionOn", "parameters": [ { "name": "objectId", "$ref": "RemoteObjectId", "description": "Identifier of the object to call function on." }, { "name": "functionDeclaration", "type": "string", "description": "Declaration of the function to call." }, { "name": "arguments", "type": "array", "items": { "$ref": "CallArgument", "description": "Call argument." }, "optional": true, "description": "Call arguments. All call arguments must belong to the same JavaScript world as the target object." }, { "name": "doNotPauseOnExceptionsAndMuteConsole", "type": "boolean", "optional": true, "description": "Specifies whether function call should stop on exceptions and mute console. Overrides setPauseOnException state.", "hidden": true }, { "name": "returnByValue", "type": "boolean", "optional": true, "description": "Whether the result is expected to be a JSON object which should be sent by value." }, { "name": "generatePreview", "type": "boolean", "optional": true, "hidden": true, "description": "Whether preview should be generated for the result." } ], "returns": [ { "name": "result", "$ref": "RemoteObject", "description": "Call result." }, { "name": "wasThrown", "type": "boolean", "optional": true, "description": "True if the result was thrown during the evaluation." } ], "description": "Calls function with given declaration on the given object. Object group of the result is inherited from the target object." }, { "name": "getProperties", "parameters": [ { "name": "objectId", "$ref": "RemoteObjectId", "description": "Identifier of the object to return properties for." }, { "name": "ownProperties", "optional": true, "type": "boolean", "description": "If true, returns properties belonging only to the element itself, not to its prototype chain." }, { "name": "accessorPropertiesOnly", "optional": true, "type": "boolean", "description": "If true, returns accessor properties (with getter/setter) only; internal properties are not returned either.", "hidden": true } ], "returns": [ { "name": "result", "type": "array", "items": { "$ref": "PropertyDescriptor"}, "description": "Object properties." }, { "name": "internalProperties", "optional": true, "type": "array", "items": { "$ref": "InternalPropertyDescriptor"}, "description": "Internal object properties (only of the element itself).", "hidden": true } ], "description": "Returns properties of a given object. Object group of the result is inherited from the target object." }, { "name": "releaseObject", "parameters": [ { "name": "objectId", "$ref": "RemoteObjectId", "description": "Identifier of the object to release." } ], "description": "Releases remote object with given id." }, { "name": "releaseObjectGroup", "parameters": [ { "name": "objectGroup", "type": "string", "description": "Symbolic object group name." } ], "description": "Releases all remote objects that belong to a given group." }, { "name": "run", "hidden": true, "description": "Tells inspected instance(worker or page) that it can run in case it was started paused." }, { "name": "enable", "description": "Enables reporting of execution contexts creation by means of executionContextCreated event. When the reporting gets enabled the event will be sent immediately for each existing execution context." }, { "name": "disable", "hidden": true, "description": "Disables reporting of execution contexts creation." } ], "events": [ { "name": "executionContextCreated", "parameters": [ { "name": "context", "$ref": "ExecutionContextDescription", "description": "A newly created execution contex." } ], "description": "Issued when new execution context is created." } ] }, { "domain": "Console", "description": "Console domain defines methods and events for interaction with the JavaScript console. Console collects messages created by means of the JavaScript Console API. One needs to enable this domain using enable command in order to start receiving the console messages. Browser collects messages issued while console domain is not enabled as well and reports them using messageAdded notification upon enabling.", "types": [ { "id": "Timestamp", "type": "number", "description": "Number of seconds since epoch.", "hidden": true }, { "id": "ConsoleMessage", "type": "object", "description": "Console message.", "properties": [ { "name": "source", "type": "string", "enum": ["xml", "javascript", "network", "console-api", "storage", "appcache", "rendering", "css", "security", "other", "deprecation"], "description": "Message source." }, { "name": "level", "type": "string", "enum": ["log", "warning", "error", "debug"], "description": "Message severity." }, { "name": "text", "type": "string", "description": "Message text." }, { "name": "type", "type": "string", "optional": true, "enum": ["log", "dir", "dirxml", "table", "trace", "clear", "startGroup", "startGroupCollapsed", "endGroup", "assert", "profile", "profileEnd"], "description": "Console message type." }, { "name": "url", "type": "string", "optional": true, "description": "URL of the message origin." }, { "name": "line", "type": "integer", "optional": true, "description": "Line number in the resource that generated this message." }, { "name": "column", "type": "integer", "optional": true, "description": "Column number in the resource that generated this message." }, { "name": "repeatCount", "type": "integer", "optional": true, "description": "Repeat count for repeated messages." }, { "name": "parameters", "type": "array", "items": { "$ref": "Runtime.RemoteObject" }, "optional": true, "description": "Message parameters in case of the formatted message." }, { "name": "stackTrace", "$ref": "StackTrace", "optional": true, "description": "JavaScript stack trace for assertions and error messages." }, { "name": "networkRequestId", "$ref": "Network.RequestId", "optional": true, "description": "Identifier of the network request associated with this message." }, { "name": "timestamp", "$ref": "Timestamp", "description": "Timestamp, when this message was fired.", "hidden": true } ] }, { "id": "CallFrame", "type": "object", "description": "Stack entry for console errors and assertions.", "properties": [ { "name": "functionName", "type": "string", "description": "JavaScript function name." }, { "name": "scriptId", "type": "string", "description": "JavaScript script id." }, { "name": "url", "type": "string", "description": "JavaScript script name or url." }, { "name": "lineNumber", "type": "integer", "description": "JavaScript script line number." }, { "name": "columnNumber", "type": "integer", "description": "JavaScript script column number." } ] }, { "id": "StackTrace", "type": "array", "items": { "$ref": "CallFrame" }, "description": "Call frames for assertions or error messages." } ], "commands": [ { "name": "enable", "description": "Enables console domain, sends the messages collected so far to the client by means of the messageAdded notification." }, { "name": "disable", "description": "Disables console domain, prevents further console messages from being reported to the client." }, { "name": "clearMessages", "description": "Clears console messages collected in the browser." }, { "name": "setMonitoringXHREnabled", "parameters": [ { "name": "enabled", "type": "boolean", "description": "Monitoring enabled state." } ], "description": "Toggles monitoring of XMLHttpRequest. If true, console will receive messages upon each XHR issued.", "hidden": true }, { "name": "addInspectedNode", "parameters": [ { "name": "nodeId", "$ref": "DOM.NodeId", "description": "DOM node id to be accessible by means of $x command line API." } ], "description": "Enables console to refer to the node with given id via $x (see Command Line API for more details $x functions).", "hidden": true }, { "name": "addInspectedHeapObject", "parameters": [ { "name": "heapObjectId", "type": "integer" } ], "hidden": true } ], "events": [ { "name": "messageAdded", "parameters": [ { "name": "message", "$ref": "ConsoleMessage", "description": "Console message that has been added." } ], "description": "Issued when new console message is added." }, { "name": "messageRepeatCountUpdated", "parameters": [ { "name": "count", "type": "integer", "description": "New repeat count value." }, { "name": "timestamp", "$ref": "Timestamp", "description": "Timestamp of most recent message in batch.", "hidden": true } ], "description": "Issued when subsequent message(s) are equal to the previous one(s)." }, { "name": "messagesCleared", "description": "Issued when console is cleared. This happens either upon clearMessages command or after page navigation." } ] }, { "domain": "Network", "description": "Network domain allows tracking network activities of the page. It exposes information about http, file, data and other requests and responses, their headers, bodies, timing, etc.", "types": [ { "id": "LoaderId", "type": "string", "description": "Unique loader identifier." }, { "id": "RequestId", "type": "string", "description": "Unique request identifier." }, { "id": "Timestamp", "type": "number", "description": "Number of seconds since epoch." }, { "id": "Headers", "type": "object", "description": "Request / response headers as keys / values of JSON object." }, { "id": "ResourceTiming", "type": "object", "description": "Timing information for the request.", "properties": [ { "name": "requestTime", "type": "number", "description": "Timing's requestTime is a baseline in seconds, while the other numbers are ticks in milliseconds relatively to this requestTime." }, { "name": "proxyStart", "type": "number", "description": "Started resolving proxy." }, { "name": "proxyEnd", "type": "number", "description": "Finished resolving proxy." }, { "name": "dnsStart", "type": "number", "description": "Started DNS address resolve." }, { "name": "dnsEnd", "type": "number", "description": "Finished DNS address resolve." }, { "name": "connectStart", "type": "number", "description": "Started connecting to the remote host." }, { "name": "connectEnd", "type": "number", "description": "Connected to the remote host." }, { "name": "sslStart", "type": "number", "description": "Started SSL handshake." }, { "name": "sslEnd", "type": "number", "description": "Finished SSL handshake." }, { "name": "sendStart", "type": "number", "description": "Started sending request." }, { "name": "sendEnd", "type": "number", "description": "Finished sending request." }, { "name": "receiveHeadersEnd", "type": "number", "description": "Finished receiving response headers." } ] }, { "id": "Request", "type": "object", "description": "HTTP request data.", "properties": [ { "name": "url", "type": "string", "description": "Request URL." }, { "name": "method", "type": "string", "description": "HTTP request method." }, { "name": "headers", "$ref": "Headers", "description": "HTTP request headers." }, { "name": "postData", "type": "string", "optional": true, "description": "HTTP POST request data." } ] }, { "id": "Response", "type": "object", "description": "HTTP response data.", "properties": [ { "name": "url", "type": "string", "description": "Response URL. This URL can be different from CachedResource.url in case of redirect." }, { "name": "status", "type": "number", "description": "HTTP response status code." }, { "name": "statusText", "type": "string", "description": "HTTP response status text." }, { "name": "headers", "$ref": "Headers", "description": "HTTP response headers." }, { "name": "headersText", "type": "string", "optional": true, "description": "HTTP response headers text." }, { "name": "mimeType", "type": "string", "description": "Resource mimeType as determined by the browser." }, { "name": "requestHeaders", "$ref": "Headers", "optional": true, "description": "Refined HTTP request headers that were actually transmitted over the network." }, { "name": "requestHeadersText", "type": "string", "optional": true, "description": "HTTP request headers text." }, { "name": "connectionReused", "type": "boolean", "description": "Specifies whether physical connection was actually reused for this request." }, { "name": "connectionId", "type": "number", "description": "Physical connection id that was actually used for this request." }, { "name": "fromDiskCache", "type": "boolean", "optional": true, "description": "Specifies that the request was served from the disk cache." }, { "name": "timing", "$ref": "ResourceTiming", "optional": true, "description": "Timing information for the given request." } ] }, { "id": "WebSocketRequest", "type": "object", "description": "WebSocket request data.", "hidden": true, "properties": [ { "name": "headers", "$ref": "Headers", "description": "HTTP response headers." } ] }, { "id": "WebSocketResponse", "type": "object", "description": "WebSocket response data.", "hidden": true, "properties": [ { "name": "status", "type": "number", "description": "HTTP response status code." }, { "name": "statusText", "type": "string", "description": "HTTP response status text." }, { "name": "headers", "$ref": "Headers", "description": "HTTP response headers." } ] }, { "id": "WebSocketFrame", "type": "object", "description": "WebSocket frame data.", "hidden": true, "properties": [ { "name": "opcode", "type": "number", "description": "WebSocket frame opcode." }, { "name": "mask", "type": "boolean", "description": "WebSocke frame mask." }, { "name": "payloadData", "type": "string", "description": "WebSocke frame payload data." } ] }, { "id": "CachedResource", "type": "object", "description": "Information about the cached resource.", "properties": [ { "name": "url", "type": "string", "description": "Resource URL. This is the url of the original network request." }, { "name": "type", "$ref": "Page.ResourceType", "description": "Type of this resource." }, { "name": "response", "$ref": "Response", "optional": true, "description": "Cached response data." }, { "name": "bodySize", "type": "number", "description": "Cached response body size." } ] }, { "id": "Initiator", "type": "object", "description": "Information about the request initiator.", "properties": [ { "name": "type", "type": "string", "enum": ["parser", "script", "other"], "description": "Type of this initiator." }, { "name": "stackTrace", "$ref": "Console.StackTrace", "optional": true, "description": "Initiator JavaScript stack trace, set for Script only." }, { "name": "url", "type": "string", "optional": true, "description": "Initiator URL, set for Parser type only." }, { "name": "lineNumber", "type": "number", "optional": true, "description": "Initiator line number, set for Parser type only." } ] } ], "commands": [ { "name": "enable", "description": "Enables network tracking, network events will now be delivered to the client." }, { "name": "disable", "description": "Disables network tracking, prevents network events from being sent to the client." }, { "name": "setUserAgentOverride", "description": "Allows overriding user agent with the given string.", "parameters": [ { "name": "userAgent", "type": "string", "description": "User agent to use." } ] }, { "name": "setExtraHTTPHeaders", "description": "Specifies whether to always send extra HTTP headers with the requests from this page.", "parameters": [ { "name": "headers", "$ref": "Headers", "description": "Map with extra HTTP headers." } ] }, { "name": "getResponseBody", "description": "Returns content served for the given request.", "parameters": [ { "name": "requestId", "$ref": "RequestId", "description": "Identifier of the network request to get content for." } ], "returns": [ { "name": "body", "type": "string", "description": "Response body." }, { "name": "base64Encoded", "type": "boolean", "description": "True, if content was sent as base64." } ] }, { "name": "replayXHR", "description": "This method sends a new XMLHttpRequest which is identical to the original one. The following parameters should be identical: method, url, async, request body, extra headers, withCredentials attribute, user, password.", "parameters": [ { "name": "requestId", "$ref": "RequestId", "description": "Identifier of XHR to replay." } ], "hidden": true }, { "name": "canClearBrowserCache", "description": "Tells whether clearing browser cache is supported.", "returns": [ { "name": "result", "type": "boolean", "description": "True if browser cache can be cleared." } ] }, { "name": "clearBrowserCache", "description": "Clears browser cache." }, { "name": "canClearBrowserCookies", "description": "Tells whether clearing browser cookies is supported.", "returns": [ { "name": "result", "type": "boolean", "description": "True if browser cookies can be cleared." } ] }, { "name": "clearBrowserCookies", "description": "Clears browser cookies." }, { "name": "setCacheDisabled", "parameters": [ { "name": "cacheDisabled", "type": "boolean", "description": "Cache disabled state." } ], "description": "Toggles ignoring cache for each request. If true, cache will not be used." }, { "name": "loadResourceForFrontend", "async": true, "parameters": [ { "name": "frameId", "$ref": "Page.FrameId", "description": "Frame to load the resource from." }, { "name": "url", "type": "string", "description": "URL of the resource to load." }, { "name": "requestHeaders", "$ref": "Network.Headers", "optional": true, "description": "Request headers." } ], "returns": [ { "name": "statusCode", "type": "number", "description": "HTTP status code." }, { "name": "responseHeaders", "$ref": "Network.Headers", "description": "Response headers." }, { "name": "content", "type": "string", "description": "Resource content." } ], "description": "Loads a resource in the context of a frame on the inspected page without cross origin checks.", "hidden": true } ], "events": [ { "name": "requestWillBeSent", "description": "Fired when page is about to send HTTP request.", "parameters": [ { "name": "requestId", "$ref": "RequestId", "description": "Request identifier." }, { "name": "frameId", "$ref": "Page.FrameId", "description": "Frame identifier.", "hidden": true }, { "name": "loaderId", "$ref": "LoaderId", "description": "Loader identifier." }, { "name": "documentURL", "type": "string", "description": "URL of the document this request is loaded for." }, { "name": "request", "$ref": "Request", "description": "Request data." }, { "name": "timestamp", "$ref": "Timestamp", "description": "Timestamp." }, { "name": "initiator", "$ref": "Initiator", "description": "Request initiator." }, { "name": "redirectResponse", "optional": true, "$ref": "Response", "description": "Redirect response data." } ] }, { "name": "requestServedFromCache", "description": "Fired if request ended up loading from cache.", "parameters": [ { "name": "requestId", "$ref": "RequestId", "description": "Request identifier." } ] }, { "name": "responseReceived", "description": "Fired when HTTP response is available.", "parameters": [ { "name": "requestId", "$ref": "RequestId", "description": "Request identifier." }, { "name": "frameId", "$ref": "Page.FrameId", "description": "Frame identifier.", "hidden": true }, { "name": "loaderId", "$ref": "LoaderId", "description": "Loader identifier." }, { "name": "timestamp", "$ref": "Timestamp", "description": "Timestamp." }, { "name": "type", "$ref": "Page.ResourceType", "description": "Resource type." }, { "name": "response", "$ref": "Response", "description": "Response data." } ] }, { "name": "dataReceived", "description": "Fired when data chunk was received over the network.", "parameters": [ { "name": "requestId", "$ref": "RequestId", "description": "Request identifier." }, { "name": "timestamp", "$ref": "Timestamp", "description": "Timestamp." }, { "name": "dataLength", "type": "integer", "description": "Data chunk length." }, { "name": "encodedDataLength", "type": "integer", "description": "Actual bytes received (might be less than dataLength for compressed encodings)." } ] }, { "name": "loadingFinished", "description": "Fired when HTTP request has finished loading.", "parameters": [ { "name": "requestId", "$ref": "RequestId", "description": "Request identifier." }, { "name": "timestamp", "$ref": "Timestamp", "description": "Timestamp." } ] }, { "name": "loadingFailed", "description": "Fired when HTTP request has failed to load.", "parameters": [ { "name": "requestId", "$ref": "RequestId", "description": "Request identifier." }, { "name": "timestamp", "$ref": "Timestamp", "description": "Timestamp." }, { "name": "errorText", "type": "string", "description": "User friendly error message." }, { "name": "canceled", "type": "boolean", "optional": true, "description": "True if loading was canceled." } ] }, { "name": "webSocketWillSendHandshakeRequest", "description": "Fired when WebSocket is about to initiate handshake.", "parameters": [ { "name": "requestId", "$ref": "RequestId", "description": "Request identifier." }, { "name": "timestamp", "$ref": "Timestamp", "description": "Timestamp." }, { "name": "request", "$ref": "WebSocketRequest", "description": "WebSocket request data." } ], "hidden": true }, { "name": "webSocketHandshakeResponseReceived", "description": "Fired when WebSocket handshake response becomes available.", "parameters": [ { "name": "requestId", "$ref": "RequestId", "description": "Request identifier." }, { "name": "timestamp", "$ref": "Timestamp", "description": "Timestamp." }, { "name": "response", "$ref": "WebSocketResponse", "description": "WebSocket response data." } ], "hidden": true }, { "name": "webSocketCreated", "description": "Fired upon WebSocket creation.", "parameters": [ { "name": "requestId", "$ref": "RequestId", "description": "Request identifier." }, { "name": "url", "type": "string", "description": "WebSocket request URL." } ], "hidden": true }, { "name": "webSocketClosed", "description": "Fired when WebSocket is closed.", "parameters": [ { "name": "requestId", "$ref": "RequestId", "description": "Request identifier." }, { "name": "timestamp", "$ref": "Timestamp", "description": "Timestamp." } ], "hidden": true }, { "name": "webSocketFrameReceived", "description": "Fired when WebSocket frame is received.", "parameters": [ { "name": "requestId", "$ref": "RequestId", "description": "Request identifier." }, { "name": "timestamp", "$ref": "Timestamp", "description": "Timestamp." }, { "name": "response", "$ref": "WebSocketFrame", "description": "WebSocket response data." } ], "hidden": true }, { "name": "webSocketFrameError", "description": "Fired when WebSocket frame error occurs.", "parameters": [ { "name": "requestId", "$ref": "RequestId", "description": "Request identifier." }, { "name": "timestamp", "$ref": "Timestamp", "description": "Timestamp." }, { "name": "errorMessage", "type": "string", "description": "WebSocket frame error message." } ], "hidden": true }, { "name": "webSocketFrameSent", "description": "Fired when WebSocket frame is sent.", "parameters": [ { "name": "requestId", "$ref": "RequestId", "description": "Request identifier." }, { "name": "timestamp", "$ref": "Timestamp", "description": "Timestamp." }, { "name": "response", "$ref": "WebSocketFrame", "description": "WebSocket response data." } ], "hidden": true } ] }, { "domain": "Database", "hidden": true, "types": [ { "id": "DatabaseId", "type": "string", "description": "Unique identifier of Database object.", "hidden": true }, { "id": "Database", "type": "object", "description": "Database object.", "hidden": true, "properties": [ { "name": "id", "$ref": "DatabaseId", "description": "Database ID." }, { "name": "domain", "type": "string", "description": "Database domain." }, { "name": "name", "type": "string", "description": "Database name." }, { "name": "version", "type": "string", "description": "Database version." } ] }, { "id": "Error", "type": "object", "description": "Database error.", "properties": [ { "name": "message", "type": "string", "description": "Error message." }, { "name": "code", "type": "integer", "description": "Error code." } ] } ], "commands": [ { "name": "enable", "description": "Enables database tracking, database events will now be delivered to the client." }, { "name": "disable", "description": "Disables database tracking, prevents database events from being sent to the client." }, { "name": "getDatabaseTableNames", "parameters": [ { "name": "databaseId", "$ref": "DatabaseId" } ], "returns": [ { "name": "tableNames", "type": "array", "items": { "type": "string" } } ] }, { "name": "executeSQL", "async": true, "parameters": [ { "name": "databaseId", "$ref": "DatabaseId" }, { "name": "query", "type": "string" } ], "returns": [ { "name": "columnNames", "type": "array", "optional": true, "items": { "type": "string" } }, { "name": "values", "type": "array", "optional": true, "items": { "type": "any" }}, { "name": "sqlError", "$ref": "Error", "optional": true } ] } ], "events": [ { "name": "addDatabase", "parameters": [ { "name": "database", "$ref": "Database" } ] } ] }, { "domain": "IndexedDB", "hidden": true, "types": [ { "id": "DatabaseWithObjectStores", "type": "object", "description": "Database with an array of object stores.", "properties": [ { "name": "name", "type": "string", "description": "Database name." }, { "name": "version", "type": "string", "description": "Deprecated string database version." }, { "name": "intVersion", "type": "integer", "description": "Integer database version." }, { "name": "objectStores", "type": "array", "items": { "$ref": "ObjectStore" }, "description": "Object stores in this database." } ] }, { "id": "ObjectStore", "type": "object", "description": "Object store.", "properties": [ { "name": "name", "type": "string", "description": "Object store name." }, { "name": "keyPath", "$ref": "KeyPath", "description": "Object store key path." }, { "name": "autoIncrement", "type": "boolean", "description": "If true, object store has auto increment flag set." }, { "name": "indexes", "type": "array", "items": { "$ref": "ObjectStoreIndex" }, "description": "Indexes in this object store." } ] }, { "id": "ObjectStoreIndex", "type": "object", "description": "Object store index.", "properties": [ { "name": "name", "type": "string", "description": "Index name." }, { "name": "keyPath", "$ref": "KeyPath", "description": "Index key path." }, { "name": "unique", "type": "boolean", "description": "If true, index is unique." }, { "name": "multiEntry", "type": "boolean", "description": "If true, index allows multiple entries for a key." } ] }, { "id": "Key", "type": "object", "description": "Key.", "properties": [ { "name": "type", "type": "string", "enum": ["number", "string", "date", "array"], "description": "Key type." }, { "name": "number", "type": "number", "optional": true, "description": "Number value." }, { "name": "string", "type": "string", "optional": true, "description": "String value." }, { "name": "date", "type": "number", "optional": true, "description": "Date value." }, { "name": "array", "type": "array", "optional": true, "items": { "$ref": "Key" }, "description": "Array value." } ] }, { "id": "KeyRange", "type": "object", "description": "Key range.", "properties": [ { "name": "lower", "$ref": "Key", "optional": true, "description": "Lower bound." }, { "name": "upper", "$ref": "Key", "optional": true, "description": "Upper bound." }, { "name": "lowerOpen", "type": "boolean", "description": "If true lower bound is open." }, { "name": "upperOpen", "type": "boolean", "description": "If true upper bound is open." } ] }, { "id": "DataEntry", "type": "object", "description": "Data entry.", "properties": [ { "name": "key", "$ref": "Runtime.RemoteObject", "description": "Key." }, { "name": "primaryKey", "$ref": "Runtime.RemoteObject", "description": "Primary key." }, { "name": "value", "$ref": "Runtime.RemoteObject", "description": "Value." } ] }, { "id": "KeyPath", "type": "object", "description": "Key path.", "properties": [ { "name": "type", "type": "string", "enum": ["null", "string", "array"], "description": "Key path type." }, { "name": "string", "type": "string", "optional": true, "description": "String value." }, { "name": "array", "type": "array", "optional": true, "items": { "type": "string" }, "description": "Array value." } ] } ], "commands": [ { "name": "enable", "description": "Enables events from backend." }, { "name": "disable", "description": "Disables events from backend." }, { "name": "requestDatabaseNames", "async": true, "parameters": [ { "name": "securityOrigin", "type": "string", "description": "Security origin." } ], "returns": [ { "name": "databaseNames", "type": "array", "items": { "type": "string" }, "description": "Database names for origin." } ], "description": "Requests database names for given security origin." }, { "name": "requestDatabase", "async": true, "parameters": [ { "name": "securityOrigin", "type": "string", "description": "Security origin." }, { "name": "databaseName", "type": "string", "description": "Database name." } ], "returns": [ { "name": "databaseWithObjectStores", "$ref": "DatabaseWithObjectStores", "description": "Database with an array of object stores." } ], "description": "Requests database with given name in given frame." }, { "name": "requestData", "async": true, "parameters": [ { "name": "securityOrigin", "type": "string", "description": "Security origin." }, { "name": "databaseName", "type": "string", "description": "Database name." }, { "name": "objectStoreName", "type": "string", "description": "Object store name." }, { "name": "indexName", "type": "string", "description": "Index name, empty string for object store data requests." }, { "name": "skipCount", "type": "integer", "description": "Number of records to skip." }, { "name": "pageSize", "type": "integer", "description": "Number of records to fetch." }, { "name": "keyRange", "$ref": "KeyRange", "optional": true, "description": "Key range." } ], "returns": [ { "name": "objectStoreDataEntries", "type": "array", "items": { "$ref": "DataEntry" }, "description": "Array of object store data entries." }, { "name": "hasMore", "type": "boolean", "description": "If true, there are more entries to fetch in the given range." } ], "description": "Requests data from object store or index." }, { "name": "clearObjectStore", "async": true, "parameters": [ { "name": "securityOrigin", "type": "string", "description": "Security origin." }, { "name": "databaseName", "type": "string", "description": "Database name." }, { "name": "objectStoreName", "type": "string", "description": "Object store name." } ], "returns": [ ], "description": "Clears all entries from an object store." } ] }, { "domain": "DOMStorage", "hidden": true, "description": "Query and modify DOM storage.", "types": [ { "id": "StorageId", "type": "object", "description": "DOM Storage identifier.", "hidden": true, "properties": [ { "name": "securityOrigin", "type": "string", "description": "Security origin for the storage." }, { "name": "isLocalStorage", "type": "boolean", "description": "Whether the storage is local storage (not session storage)." } ] }, { "id": "Item", "type": "array", "description": "DOM Storage item.", "hidden": true, "items": { "type": "string" } } ], "commands": [ { "name": "enable", "description": "Enables storage tracking, storage events will now be delivered to the client." }, { "name": "disable", "description": "Disables storage tracking, prevents storage events from being sent to the client." }, { "name": "getDOMStorageItems", "parameters": [ { "name": "storageId", "$ref": "StorageId" } ], "returns": [ { "name": "entries", "type": "array", "items": { "$ref": "Item" } } ] }, { "name": "setDOMStorageItem", "parameters": [ { "name": "storageId", "$ref": "StorageId" }, { "name": "key", "type": "string" }, { "name": "value", "type": "string" } ] }, { "name": "removeDOMStorageItem", "parameters": [ { "name": "storageId", "$ref": "StorageId" }, { "name": "key", "type": "string" } ] } ], "events": [ { "name": "domStorageItemsCleared", "parameters": [ { "name": "storageId", "$ref": "StorageId" } ] }, { "name": "domStorageItemRemoved", "parameters": [ { "name": "storageId", "$ref": "StorageId" }, { "name": "key", "type": "string" } ] }, { "name": "domStorageItemAdded", "parameters": [ { "name": "storageId", "$ref": "StorageId" }, { "name": "key", "type": "string" }, { "name": "newValue", "type": "string" } ] }, { "name": "domStorageItemUpdated", "parameters": [ { "name": "storageId", "$ref": "StorageId" }, { "name": "key", "type": "string" }, { "name": "oldValue", "type": "string" }, { "name": "newValue", "type": "string" } ] } ] }, { "domain": "ApplicationCache", "hidden": true, "types": [ { "id": "ApplicationCacheResource", "type": "object", "description": "Detailed application cache resource information.", "properties": [ { "name": "url", "type": "string", "description": "Resource url." }, { "name": "size", "type": "integer", "description": "Resource size." }, { "name": "type", "type": "string", "description": "Resource type." } ] }, { "id": "ApplicationCache", "type": "object", "description": "Detailed application cache information.", "properties": [ { "name": "manifestURL", "type": "string", "description": "Manifest URL." }, { "name": "size", "type": "number", "description": "Application cache size." }, { "name": "creationTime", "type": "number", "description": "Application cache creation time." }, { "name": "updateTime", "type": "number", "description": "Application cache update time." }, { "name": "resources", "type": "array", "items": { "$ref": "ApplicationCacheResource" }, "description": "Application cache resources." } ] }, { "id": "FrameWithManifest", "type": "object", "description": "Frame identifier - manifest URL pair.", "properties": [ { "name": "frameId", "$ref": "Page.FrameId", "description": "Frame identifier." }, { "name": "manifestURL", "type": "string", "description": "Manifest URL." }, { "name": "status", "type": "integer", "description": "Application cache status." } ] } ], "commands": [ { "name": "getFramesWithManifests", "returns": [ { "name": "frameIds", "type": "array", "items": { "$ref": "FrameWithManifest" }, "description": "Array of frame identifiers with manifest urls for each frame containing a document associated with some application cache." } ], "description": "Returns array of frame identifiers with manifest urls for each frame containing a document associated with some application cache." }, { "name": "enable", "description": "Enables application cache domain notifications." }, { "name": "getManifestForFrame", "parameters": [ { "name": "frameId", "$ref": "Page.FrameId", "description": "Identifier of the frame containing document whose manifest is retrieved." } ], "returns": [ { "name": "manifestURL", "type": "string", "description": "Manifest URL for document in the given frame." } ], "description": "Returns manifest URL for document in the given frame." }, { "name": "getApplicationCacheForFrame", "parameters": [ { "name": "frameId", "$ref": "Page.FrameId", "description": "Identifier of the frame containing document whose application cache is retrieved." } ], "returns": [ { "name": "applicationCache", "$ref": "ApplicationCache", "description": "Relevant application cache data for the document in given frame." } ], "description": "Returns relevant application cache data for the document in given frame." } ], "events": [ { "name": "applicationCacheStatusUpdated", "parameters": [ { "name": "frameId", "$ref": "Page.FrameId", "description": "Identifier of the frame containing document whose application cache updated status." }, { "name": "manifestURL", "type": "string", "description": "Manifest URL." }, { "name": "status", "type": "integer", "description": "Updated application cache status." } ] }, { "name": "networkStateUpdated", "parameters": [ { "name": "isNowOnline", "type": "boolean" } ] } ] }, { "domain": "FileSystem", "hidden": true, "types": [ { "id": "Entry", "type": "object", "properties": [ { "name": "url", "type": "string", "description": "filesystem: URL for the entry." }, { "name": "name", "type": "string", "description": "The name of the file or directory." }, { "name": "isDirectory", "type": "boolean", "description": "True if the entry is a directory." }, { "name": "mimeType", "type": "string", "optional": true, "description": "MIME type of the entry, available for a file only." }, { "name": "resourceType", "$ref": "Page.ResourceType", "optional": true, "description": "ResourceType of the entry, available for a file only." }, { "name": "isTextFile", "type": "boolean", "optional": true, "description": "True if the entry is a text file." } ], "description": "Represents a browser side file or directory." }, { "id": "Metadata", "type": "object", "properties": [ { "name": "modificationTime", "type": "number", "description": "Modification time." }, { "name": "size", "type": "number", "description": "File size. This field is always zero for directories." } ], "description": "Represents metadata of a file or entry." } ], "commands": [ { "name": "enable", "description": "Enables events from backend." }, { "name": "disable", "description": "Disables events from backend." }, { "name": "requestFileSystemRoot", "async": true, "parameters": [ { "name": "origin", "type": "string", "description": "Security origin of requesting FileSystem. One of frames in current page needs to have this security origin." }, { "name": "type", "type": "string", "enum": ["temporary", "persistent"], "description": "FileSystem type of requesting FileSystem." } ], "returns": [ { "name": "errorCode", "type": "integer", "description": "0, if no error. Otherwise, errorCode is set to FileError::ErrorCode value." }, { "name": "root", "$ref": "Entry", "optional": true, "description": "Contains root of the requested FileSystem if the command completed successfully." } ], "description": "Returns root directory of the FileSystem, if exists." }, { "name": "requestDirectoryContent", "async": true, "parameters": [ { "name": "url", "type": "string", "description": "URL of the directory that the frontend is requesting to read from." } ], "returns": [ { "name": "errorCode", "type": "integer", "description": "0, if no error. Otherwise, errorCode is set to FileError::ErrorCode value." }, { "name": "entries", "type": "array", "items": { "$ref": "Entry" }, "optional": true, "description": "Contains all entries on directory if the command completed successfully." } ], "description": "Returns content of the directory." }, { "name": "requestMetadata", "async": true, "parameters": [ { "name": "url", "type": "string", "description": "URL of the entry that the frontend is requesting to get metadata from." } ], "returns": [ { "name": "errorCode", "type": "integer", "description": "0, if no error. Otherwise, errorCode is set to FileError::ErrorCode value." }, { "name": "metadata", "$ref": "Metadata", "optional": true, "description": "Contains metadata of the entry if the command completed successfully." } ], "description": "Returns metadata of the entry." }, { "name": "requestFileContent", "async": true, "parameters": [ { "name": "url", "type": "string", "description": "URL of the file that the frontend is requesting to read from." }, { "name": "readAsText", "type": "boolean", "description": "True if the content should be read as text, otherwise the result will be returned as base64 encoded text." }, { "name": "start", "type": "integer", "optional": true, "description": "Specifies the start of range to read." }, { "name": "end", "type": "integer", "optional": true, "description": "Specifies the end of range to read exclusively." }, { "name": "charset", "type": "string", "optional": true, "description": "Overrides charset of the content when content is served as text." } ], "returns": [ { "name": "errorCode", "type": "integer", "description": "0, if no error. Otherwise, errorCode is set to FileError::ErrorCode value." }, { "name": "content", "type": "string", "optional": true, "description": "Content of the file." }, { "name": "charset", "type": "string", "optional": true, "description": "Charset of the content if it is served as text." } ], "description": "Returns content of the file. Result should be sliced into [start, end)." }, { "name": "deleteEntry", "async": true, "parameters": [ { "name": "url", "type": "string", "description": "URL of the entry to delete." } ], "returns": [ { "name": "errorCode", "type": "integer", "description": "0, if no error. Otherwise errorCode is set to FileError::ErrorCode value." } ], "description": "Deletes specified entry. If the entry is a directory, the agent deletes children recursively." } ] }, { "domain": "DOM", "description": "This domain exposes DOM read/write operations. Each DOM Node is represented with its mirror object that has an id. This id can be used to get additional information on the Node, resolve it into the JavaScript object wrapper, etc. It is important that client receives DOM events only for the nodes that are known to the client. Backend keeps track of the nodes that were sent to the client and never sends the same node twice. It is client's responsibility to collect information about the nodes that were sent to the client.

Note that iframe owner elements will return corresponding document elements as their child nodes.

", "types": [ { "id": "NodeId", "type": "integer", "description": "Unique DOM node identifier." }, { "id": "BackendNodeId", "type": "integer", "description": "Unique DOM node identifier used to reference a node that may not have been pushed to the front-end.", "hidden": true }, { "id": "PseudoType", "type": "string", "enum": ["before", "after"], "description": "Pseudo element type." }, { "id": "Node", "type": "object", "properties": [ { "name": "nodeId", "$ref": "NodeId", "description": "Node identifier that is passed into the rest of the DOM messages as the nodeId. Backend will only push node with given id once. It is aware of all requested nodes and will only fire DOM events for nodes known to the client." }, { "name": "nodeType", "type": "integer", "description": "Node's nodeType." }, { "name": "nodeName", "type": "string", "description": "Node's nodeName." }, { "name": "localName", "type": "string", "description": "Node's localName." }, { "name": "nodeValue", "type": "string", "description": "Node's nodeValue." }, { "name": "childNodeCount", "type": "integer", "optional": true, "description": "Child count for Container nodes." }, { "name": "children", "type": "array", "optional": true, "items": { "$ref": "Node" }, "description": "Child nodes of this node when requested with children." }, { "name": "attributes", "type": "array", "optional": true, "items": { "type": "string" }, "description": "Attributes of the Element node in the form of flat array [name1, value1, name2, value2]." }, { "name": "documentURL", "type": "string", "optional": true, "description": "Document URL that Document or FrameOwner node points to." }, { "name": "baseURL", "type": "string", "optional": true, "description": "Base URL that Document or FrameOwner node uses for URL completion.", "hidden": true }, { "name": "publicId", "type": "string", "optional": true, "description": "DocumentType's publicId." }, { "name": "systemId", "type": "string", "optional": true, "description": "DocumentType's systemId." }, { "name": "internalSubset", "type": "string", "optional": true, "description": "DocumentType's internalSubset." }, { "name": "xmlVersion", "type": "string", "optional": true, "description": "Document's XML version in case of XML documents." }, { "name": "name", "type": "string", "optional": true, "description": "Attr's name." }, { "name": "value", "type": "string", "optional": true, "description": "Attr's value." }, { "name": "pseudoType", "$ref": "PseudoType", "optional": true, "description": "Pseudo element type for this node." }, { "name": "frameId", "$ref": "Page.FrameId", "optional": true, "description": "Frame ID for frame owner elements.", "hidden": true }, { "name": "contentDocument", "$ref": "Node", "optional": true, "description": "Content document for frame owner elements." }, { "name": "shadowRoots", "type": "array", "optional": true, "items": { "$ref": "Node" }, "description": "Shadow root list for given element host.", "hidden": true }, { "name": "templateContent", "$ref": "Node", "optional": true, "description": "Content document fragment for template elements.", "hidden": true }, { "name": "pseudoElements", "type": "array", "items": { "$ref": "Node" }, "optional": true, "description": "Pseudo elements associated with this node.", "hidden": true } ], "description": "DOM interaction is implemented in terms of mirror objects that represent the actual DOM nodes. DOMNode is a base node mirror type." }, { "id": "EventListener", "type": "object", "hidden": true, "properties": [ { "name": "type", "type": "string", "description": "EventListener's type." }, { "name": "useCapture", "type": "boolean", "description": "EventListener's useCapture." }, { "name": "isAttribute", "type": "boolean", "description": "EventListener's isAttribute." }, { "name": "nodeId", "$ref": "NodeId", "description": "Target DOMNode id." }, { "name": "handlerBody", "type": "string", "description": "Event handler function body." }, { "name": "location", "$ref": "Debugger.Location", "optional": true, "description": "Handler code location." }, { "name": "sourceName", "type": "string", "optional": true, "description": "Source script URL." }, { "name": "handler", "$ref": "Runtime.RemoteObject", "optional": true, "description": "Event handler function value." } ], "description": "DOM interaction is implemented in terms of mirror objects that represent the actual DOM nodes. DOMNode is a base node mirror type." }, { "id": "RGBA", "type": "object", "properties": [ { "name": "r", "type": "integer", "description": "The red component, in the [0-255] range." }, { "name": "g", "type": "integer", "description": "The green component, in the [0-255] range." }, { "name": "b", "type": "integer", "description": "The blue component, in the [0-255] range." }, { "name": "a", "type": "number", "optional": true, "description": "The alpha component, in the [0-1] range (default: 1)." } ], "description": "A structure holding an RGBA color." }, { "id": "Quad", "type": "array", "items": { "type": "number" }, "minItems": 8, "maxItems": 8, "description": "An array of quad vertices, x immediately followed by y for each point, points clock-wise.", "hidden": true }, { "id": "BoxModel", "type": "object", "hidden": true, "properties": [ { "name": "content", "$ref": "Quad", "description": "Content box" }, { "name": "padding", "$ref": "Quad", "description": "Padding box" }, { "name": "border", "$ref": "Quad", "description": "Border box" }, { "name": "margin", "$ref": "Quad", "description": "Margin box" }, { "name": "width", "type": "integer", "description": "Node width" }, { "name": "height", "type": "integer", "description": "Node height" } ], "description": "Box model." }, { "id": "Rect", "type": "object", "hidden": true, "properties": [ { "name": "x", "type": "number", "description": "X coordinate" }, { "name": "y", "type": "number", "description": "Y coordinate" }, { "name": "width", "type": "number", "description": "Rectangle width" }, { "name": "height", "type": "number", "description": "Rectangle height" } ], "description": "Rectangle." }, { "id": "HighlightConfig", "type": "object", "properties": [ { "name": "showInfo", "type": "boolean", "optional": true, "description": "Whether the node info tooltip should be shown (default: false)." }, { "name": "contentColor", "$ref": "RGBA", "optional": true, "description": "The content box highlight fill color (default: transparent)." }, { "name": "paddingColor", "$ref": "RGBA", "optional": true, "description": "The padding highlight fill color (default: transparent)." }, { "name": "borderColor", "$ref": "RGBA", "optional": true, "description": "The border highlight fill color (default: transparent)." }, { "name": "marginColor", "$ref": "RGBA", "optional": true, "description": "The margin highlight fill color (default: transparent)." }, { "name": "eventTargetColor", "$ref": "RGBA", "optional": true, "hidden": true, "description": "The event target element highlight fill color (default: transparent)." } ], "description": "Configuration data for the highlighting of page elements." } ], "commands": [ { "name": "enable", "description": "Enables DOM agent for the given page." }, { "name": "disable", "description": "Disables DOM agent for the given page." }, { "name": "getDocument", "returns": [ { "name": "root", "$ref": "Node", "description": "Resulting node." } ], "description": "Returns the root DOM node to the caller." }, { "name": "requestChildNodes", "parameters": [ { "name": "nodeId", "$ref": "NodeId", "description": "Id of the node to get children for." }, { "name": "depth", "type": "integer", "optional": true, "description": "The maximum depth at which children should be retrieved, defaults to 1. Use -1 for the entire subtree or provide an integer larger than 0.", "hidden": true } ], "description": "Requests that children of the node with given id are returned to the caller in form of setChildNodes events where not only immediate children are retrieved, but all children down to the specified depth." }, { "name": "querySelector", "parameters": [ { "name": "nodeId", "$ref": "NodeId", "description": "Id of the node to query upon." }, { "name": "selector", "type": "string", "description": "Selector string." } ], "returns": [ { "name": "nodeId", "$ref": "NodeId", "description": "Query selector result." } ], "description": "Executes querySelector on a given node." }, { "name": "querySelectorAll", "parameters": [ { "name": "nodeId", "$ref": "NodeId", "description": "Id of the node to query upon." }, { "name": "selector", "type": "string", "description": "Selector string." } ], "returns": [ { "name": "nodeIds", "type": "array", "items": { "$ref": "NodeId" }, "description": "Query selector result." } ], "description": "Executes querySelectorAll on a given node." }, { "name": "setNodeName", "parameters": [ { "name": "nodeId", "$ref": "NodeId", "description": "Id of the node to set name for." }, { "name": "name", "type": "string", "description": "New node's name." } ], "returns": [ { "name": "nodeId", "$ref": "NodeId", "description": "New node's id." } ], "description": "Sets node name for a node with given id." }, { "name": "setNodeValue", "parameters": [ { "name": "nodeId", "$ref": "NodeId", "description": "Id of the node to set value for." }, { "name": "value", "type": "string", "description": "New node's value." } ], "description": "Sets node value for a node with given id." }, { "name": "removeNode", "parameters": [ { "name": "nodeId", "$ref": "NodeId", "description": "Id of the node to remove." } ], "description": "Removes node with given id." }, { "name": "setAttributeValue", "parameters": [ { "name": "nodeId", "$ref": "NodeId", "description": "Id of the element to set attribute for." }, { "name": "name", "type": "string", "description": "Attribute name." }, { "name": "value", "type": "string", "description": "Attribute value." } ], "description": "Sets attribute for an element with given id." }, { "name": "setAttributesAsText", "parameters": [ { "name": "nodeId", "$ref": "NodeId", "description": "Id of the element to set attributes for." }, { "name": "text", "type": "string", "description": "Text with a number of attributes. Will parse this text using HTML parser." }, { "name": "name", "type": "string", "optional": true, "description": "Attribute name to replace with new attributes derived from text in case text parsed successfully." } ], "description": "Sets attributes on element with given id. This method is useful when user edits some existing attribute value and types in several attribute name/value pairs." }, { "name": "removeAttribute", "parameters": [ { "name": "nodeId", "$ref": "NodeId", "description": "Id of the element to remove attribute from." }, { "name": "name", "type": "string", "description": "Name of the attribute to remove." } ], "description": "Removes attribute with given name from an element with given id." }, { "name": "getEventListenersForNode", "parameters": [ { "name": "nodeId", "$ref": "NodeId", "description": "Id of the node to get listeners for." }, { "name": "objectGroup", "type": "string", "optional": true, "description": "Symbolic group name for handler value. Handler value is not returned without this parameter specified." } ], "returns": [ { "name": "listeners", "type": "array", "items": { "$ref": "EventListener"}, "description": "Array of relevant listeners." } ], "description": "Returns event listeners relevant to the node.", "hidden": true }, { "name": "getOuterHTML", "parameters": [ { "name": "nodeId", "$ref": "NodeId", "description": "Id of the node to get markup for." } ], "returns": [ { "name": "outerHTML", "type": "string", "description": "Outer HTML markup." } ], "description": "Returns node's HTML markup." }, { "name": "setOuterHTML", "parameters": [ { "name": "nodeId", "$ref": "NodeId", "description": "Id of the node to set markup for." }, { "name": "outerHTML", "type": "string", "description": "Outer HTML markup to set." } ], "description": "Sets node HTML markup, returns new node id." }, { "name": "performSearch", "parameters": [ { "name": "query", "type": "string", "description": "Plain text or query selector or XPath search query." } ], "returns": [ { "name": "searchId", "type": "string", "description": "Unique search session identifier." }, { "name": "resultCount", "type": "integer", "description": "Number of search results." } ], "description": "Searches for a given string in the DOM tree. Use getSearchResults to access search results or cancelSearch to end this search session.", "hidden": true }, { "name": "getSearchResults", "parameters": [ { "name": "searchId", "type": "string", "description": "Unique search session identifier." }, { "name": "fromIndex", "type": "integer", "description": "Start index of the search result to be returned." }, { "name": "toIndex", "type": "integer", "description": "End index of the search result to be returned." } ], "returns": [ { "name": "nodeIds", "type": "array", "items": { "$ref": "NodeId" }, "description": "Ids of the search result nodes." } ], "description": "Returns search results from given fromIndex to given toIndex from the sarch with the given identifier.", "hidden": true }, { "name": "discardSearchResults", "parameters": [ { "name": "searchId", "type": "string", "description": "Unique search session identifier." } ], "description": "Discards search results from the session with the given id. getSearchResults should no longer be called for that search.", "hidden": true }, { "name": "requestNode", "parameters": [ { "name": "objectId", "$ref": "Runtime.RemoteObjectId", "description": "JavaScript object id to convert into node." } ], "returns": [ { "name": "nodeId", "$ref": "NodeId", "description": "Node id for given object." } ], "description": "Requests that the node is sent to the caller given the JavaScript node object reference. All nodes that form the path from the node to the root are also sent to the client as a series of setChildNodes notifications." }, { "name": "setInspectModeEnabled", "hidden": true, "parameters": [ { "name": "enabled", "type": "boolean", "description": "True to enable inspection mode, false to disable it." }, { "name": "inspectShadowDOM", "type": "boolean", "optional": true, "description": "True to enable inspection mode for shadow DOM." }, { "name": "highlightConfig", "$ref": "HighlightConfig", "optional": true, "description": "A descriptor for the highlight appearance of hovered-over nodes. May be omitted if enabled == false." } ], "description": "Enters the 'inspect' mode. In this mode, elements that user is hovering over are highlighted. Backend then generates 'inspectNodeRequested' event upon element selection." }, { "name": "highlightRect", "parameters": [ { "name": "x", "type": "integer", "description": "X coordinate" }, { "name": "y", "type": "integer", "description": "Y coordinate" }, { "name": "width", "type": "integer", "description": "Rectangle width" }, { "name": "height", "type": "integer", "description": "Rectangle height" }, { "name": "color", "$ref": "RGBA", "optional": true, "description": "The highlight fill color (default: transparent)." }, { "name": "outlineColor", "$ref": "RGBA", "optional": true, "description": "The highlight outline color (default: transparent)." } ], "description": "Highlights given rectangle. Coordinates are absolute with respect to the main frame viewport." }, { "name": "highlightQuad", "parameters": [ { "name": "quad", "$ref": "Quad", "description": "Quad to highlight" }, { "name": "color", "$ref": "RGBA", "optional": true, "description": "The highlight fill color (default: transparent)." }, { "name": "outlineColor", "$ref": "RGBA", "optional": true, "description": "The highlight outline color (default: transparent)." } ], "description": "Highlights given quad. Coordinates are absolute with respect to the main frame viewport.", "hidden": true }, { "name": "highlightNode", "parameters": [ { "name": "highlightConfig", "$ref": "HighlightConfig", "description": "A descriptor for the highlight appearance." }, { "name": "nodeId", "$ref": "NodeId", "optional": true, "description": "Identifier of the node to highlight." }, { "name": "objectId", "$ref": "Runtime.RemoteObjectId", "optional": true, "description": "JavaScript object id of the node to be highlighted.", "hidden": true } ], "description": "Highlights DOM node with given id or with the given JavaScript object wrapper. Either nodeId or objectId must be specified." }, { "name": "hideHighlight", "description": "Hides DOM node highlight." }, { "name": "highlightFrame", "parameters": [ { "name": "frameId", "$ref": "Page.FrameId", "description": "Identifier of the frame to highlight." }, { "name": "contentColor", "$ref": "RGBA", "optional": true, "description": "The content box highlight fill color (default: transparent)." }, { "name": "contentOutlineColor", "$ref": "RGBA", "optional": true, "description": "The content box highlight outline color (default: transparent)." } ], "description": "Highlights owner element of the frame with given id.", "hidden": true }, { "name": "pushNodeByPathToFrontend", "parameters": [ { "name": "path", "type": "string", "description": "Path to node in the proprietary format." } ], "returns": [ { "name": "nodeId", "$ref": "NodeId", "description": "Id of the node for given path." } ], "description": "Requests that the node is sent to the caller given its path. // FIXME, use XPath", "hidden": true }, { "name": "pushNodeByBackendIdToFrontend", "parameters": [ { "name": "backendNodeId", "$ref": "BackendNodeId", "description": "The backend node id of the node." } ], "returns": [ { "name": "nodeId", "$ref": "NodeId", "description": "The pushed node's id." } ], "description": "Requests that the node is sent to the caller given its backend node id.", "hidden": true }, { "name": "releaseBackendNodeIds", "parameters": [ { "name": "nodeGroup", "type": "string", "description": "The backend node ids group name." } ], "description": "Requests that group of BackendNodeIds is released.", "hidden": true }, { "name": "resolveNode", "parameters": [ { "name": "nodeId", "$ref": "NodeId", "description": "Id of the node to resolve." }, { "name": "objectGroup", "type": "string", "optional": true, "description": "Symbolic group name that can be used to release multiple objects." } ], "returns": [ { "name": "object", "$ref": "Runtime.RemoteObject", "description": "JavaScript object wrapper for given node." } ], "description": "Resolves JavaScript node object for given node id." }, { "name": "getAttributes", "parameters": [ { "name": "nodeId", "$ref": "NodeId", "description": "Id of the node to retrieve attibutes for." } ], "returns": [ { "name": "attributes", "type": "array", "items": { "type": "string" }, "description": "An interleaved array of node attribute names and values." } ], "description": "Returns attributes for the specified node." }, { "name": "moveTo", "parameters": [ { "name": "nodeId", "$ref": "NodeId", "description": "Id of the node to drop." }, { "name": "targetNodeId", "$ref": "NodeId", "description": "Id of the element to drop into." }, { "name": "insertBeforeNodeId", "$ref": "NodeId", "optional": true, "description": "Drop node before given one." } ], "returns": [ { "name": "nodeId", "$ref": "NodeId", "description": "New id of the moved node." } ], "description": "Moves node into the new container, places it before the given anchor." }, { "name": "undo", "description": "Undoes the last performed action.", "hidden": true }, { "name": "redo", "description": "Re-does the last undone action.", "hidden": true }, { "name": "markUndoableState", "description": "Marks last undoable state.", "hidden": true }, { "name": "focus", "parameters": [ { "name": "nodeId", "$ref": "NodeId", "description": "Id of the node to focus." } ], "description": "Focuses the given element.", "hidden": true }, { "name": "setFileInputFiles", "parameters": [ { "name": "nodeId", "$ref": "NodeId", "description": "Id of the file input node to set files for." }, { "name": "files", "type": "array", "items": { "type": "string" }, "description": "Array of file paths to set." } ], "description": "Sets files for the given file input element.", "hidden": true }, { "name": "getBoxModel", "parameters": [ { "name": "nodeId", "$ref": "NodeId", "description": "Id of the node to get box model for." } ], "returns": [ { "name": "model", "$ref": "BoxModel", "description": "Box model for the node." } ], "description": "Returns boxes for the currently selected nodes.", "hidden": true }, { "name": "getNodeForLocation", "parameters": [ { "name": "x", "type": "integer", "description": "X coordinate." }, { "name": "y", "type": "integer", "description": "Y coordinate." } ], "returns": [ { "name": "nodeId", "$ref": "NodeId", "description": "Id of the node at given coordinates." } ], "description": "Returns node id at given location.", "hidden": true } ], "events": [ { "name": "documentUpdated", "description": "Fired when Document has been totally updated. Node ids are no longer valid." }, { "name": "inspectNodeRequested", "parameters": [ { "name": "nodeId", "$ref": "NodeId", "description": "Id of the node to inspect." } ], "description": "Fired when the node should be inspected. This happens after call to setInspectModeEnabled.", "hidden" : true }, { "name": "setChildNodes", "parameters": [ { "name": "parentId", "$ref": "NodeId", "description": "Parent node id to populate with children." }, { "name": "nodes", "type": "array", "items": { "$ref": "Node"}, "description": "Child nodes array." } ], "description": "Fired when backend wants to provide client with the missing DOM structure. This happens upon most of the calls requesting node ids." }, { "name": "attributeModified", "parameters": [ { "name": "nodeId", "$ref": "NodeId", "description": "Id of the node that has changed." }, { "name": "name", "type": "string", "description": "Attribute name." }, { "name": "value", "type": "string", "description": "Attribute value." } ], "description": "Fired when Element's attribute is modified." }, { "name": "attributeRemoved", "parameters": [ { "name": "nodeId", "$ref": "NodeId", "description": "Id of the node that has changed." }, { "name": "name", "type": "string", "description": "A ttribute name." } ], "description": "Fired when Element's attribute is removed." }, { "name": "inlineStyleInvalidated", "parameters": [ { "name": "nodeIds", "type": "array", "items": { "$ref": "NodeId" }, "description": "Ids of the nodes for which the inline styles have been invalidated." } ], "description": "Fired when Element's inline style is modified via a CSS property modification.", "hidden": true }, { "name": "characterDataModified", "parameters": [ { "name": "nodeId", "$ref": "NodeId", "description": "Id of the node that has changed." }, { "name": "characterData", "type": "string", "description": "New text value." } ], "description": "Mirrors DOMCharacterDataModified event." }, { "name": "childNodeCountUpdated", "parameters": [ { "name": "nodeId", "$ref": "NodeId", "description": "Id of the node that has changed." }, { "name": "childNodeCount", "type": "integer", "description": "New node count." } ], "description": "Fired when Container's child node count has changed." }, { "name": "childNodeInserted", "parameters": [ { "name": "parentNodeId", "$ref": "NodeId", "description": "Id of the node that has changed." }, { "name": "previousNodeId", "$ref": "NodeId", "description": "If of the previous siblint." }, { "name": "node", "$ref": "Node", "description": "Inserted node data." } ], "description": "Mirrors DOMNodeInserted event." }, { "name": "childNodeRemoved", "parameters": [ { "name": "parentNodeId", "$ref": "NodeId", "description": "Parent id." }, { "name": "nodeId", "$ref": "NodeId", "description": "Id of the node that has been removed." } ], "description": "Mirrors DOMNodeRemoved event." }, { "name": "shadowRootPushed", "parameters": [ { "name": "hostId", "$ref": "NodeId", "description": "Host element id." }, { "name": "root", "$ref": "Node", "description": "Shadow root." } ], "description": "Called when shadow root is pushed into the element.", "hidden": true }, { "name": "shadowRootPopped", "parameters": [ { "name": "hostId", "$ref": "NodeId", "description": "Host element id." }, { "name": "rootId", "$ref": "NodeId", "description": "Shadow root id." } ], "description": "Called when shadow root is popped from the element.", "hidden": true }, { "name": "pseudoElementAdded", "parameters": [ { "name": "parentId", "$ref": "NodeId", "description": "Pseudo element's parent element id." }, { "name": "pseudoElement", "$ref": "Node", "description": "The added pseudo element." } ], "description": "Called when a pseudo element is added to an element.", "hidden": true }, { "name": "pseudoElementRemoved", "parameters": [ { "name": "parentId", "$ref": "NodeId", "description": "Pseudo element's parent element id." }, { "name": "pseudoElementId", "$ref": "NodeId", "description": "The removed pseudo element id." } ], "description": "Called when a pseudo element is removed from an element.", "hidden": true } ] }, { "domain": "CSS", "hidden": true, "description": "This domain exposes CSS read/write operations. All CSS objects (stylesheets, rules, and styles) have an associated id used in subsequent operations on the related object. Each object type has a specific id structure, and those are not interchangeable between objects of different kinds. CSS objects can be loaded using the get*ForNode() calls (which accept a DOM node id). A client can also discover all the existing stylesheets with the getAllStyleSheets() method (or keeping track of the styleSheetAdded/styleSheetRemoved events) and subsequently load the required stylesheet contents using the getStyleSheet[Text]() methods.", "types": [ { "id": "StyleSheetId", "type": "string" }, { "id": "CSSStyleId", "type": "object", "properties": [ { "name": "styleSheetId", "$ref": "StyleSheetId", "description": "Enclosing stylesheet identifier." }, { "name": "ordinal", "type": "integer", "description": "The style ordinal within the stylesheet." } ], "description": "This object identifies a CSS style in a unique way." }, { "id": "StyleSheetOrigin", "type": "string", "enum": ["user", "user-agent", "inspector", "regular"], "description": "Stylesheet type: \"user\" for user stylesheets, \"user-agent\" for user-agent stylesheets, \"inspector\" for stylesheets created by the inspector (i.e. those holding the \"via inspector\" rules), \"regular\" for regular stylesheets." }, { "id": "CSSRuleId", "type": "object", "properties": [ { "name": "styleSheetId", "$ref": "StyleSheetId", "description": "Enclosing stylesheet identifier." }, { "name": "ordinal", "type": "integer", "description": "The rule ordinal within the stylesheet." } ], "description": "This object identifies a CSS rule in a unique way." }, { "id": "PseudoIdMatches", "type": "object", "properties": [ { "name": "pseudoId", "type": "integer", "description": "Pseudo style identifier (see enum PseudoId in RenderStyleConstants.h)."}, { "name": "matches", "type": "array", "items": { "$ref": "RuleMatch" }, "description": "Matches of CSS rules applicable to the pseudo style."} ], "description": "CSS rule collection for a single pseudo style." }, { "id": "InheritedStyleEntry", "type": "object", "properties": [ { "name": "inlineStyle", "$ref": "CSSStyle", "optional": true, "description": "The ancestor node's inline style, if any, in the style inheritance chain." }, { "name": "matchedCSSRules", "type": "array", "items": { "$ref": "RuleMatch" }, "description": "Matches of CSS rules matching the ancestor node in the style inheritance chain." } ], "description": "CSS rule collection for a single pseudo style." }, { "id": "RuleMatch", "type": "object", "properties": [ { "name": "rule", "$ref": "CSSRule", "description": "CSS rule in the match." }, { "name": "matchingSelectors", "type": "array", "items": { "type": "integer" }, "description": "Matching selector indices in the rule's selectorList selectors (0-based)." } ], "description": "Match data for a CSS rule." }, { "id": "SelectorList", "type": "object", "properties": [ { "name": "selectors", "type": "array", "items": { "type": "string" }, "description": "Selectors in the list." }, { "name": "text", "type": "string", "description": "Rule selector text." }, { "name": "range", "$ref": "SourceRange", "optional": true, "description": "Rule selector range in the underlying resource (if available)." } ], "description": "Selector list data." }, { "id": "CSSStyleAttribute", "type": "object", "properties": [ { "name": "name", "type": "string", "description": "DOM attribute name (e.g. \"width\")."}, { "name": "style", "$ref": "CSSStyle", "description": "CSS style generated by the respective DOM attribute."} ], "description": "CSS style information for a DOM style attribute." }, { "id": "CSSStyleSheetHeader", "type": "object", "properties": [ { "name": "styleSheetId", "$ref": "StyleSheetId", "description": "The stylesheet identifier."}, { "name": "frameId", "$ref": "Page.FrameId", "description": "Owner frame identifier."}, { "name": "sourceURL", "type": "string", "description": "Stylesheet resource URL."}, { "name": "sourceMapURL", "type": "string", "optional": true, "description": "URL of source map associated with the stylesheet (if any)." }, { "name": "origin", "$ref": "StyleSheetOrigin", "description": "Stylesheet origin."}, { "name": "title", "type": "string", "description": "Stylesheet title."}, { "name": "disabled", "type": "boolean", "description": "Denotes whether the stylesheet is disabled."}, { "name": "hasSourceURL", "type": "boolean", "optional": true, "description": "Whether the sourceURL field value comes from the sourceURL comment." }, { "name": "isInline", "type": "boolean", "description": "Whether this stylesheet is created for STYLE tag by parser. This flag is not set for document.written STYLE tags." }, { "name": "startLine", "type": "number", "description": "Line offset of the stylesheet within the resource (zero based)." }, { "name": "startColumn", "type": "number", "description": "Column offset of the stylesheet within the resource (zero based)." } ], "description": "CSS stylesheet metainformation." }, { "id": "CSSStyleSheetBody", "type": "object", "properties": [ { "name": "styleSheetId", "$ref": "StyleSheetId", "description": "The stylesheet identifier."}, { "name": "rules", "type": "array", "items": { "$ref": "CSSRule" }, "description": "Stylesheet resource URL."}, { "name": "text", "type": "string", "optional": true, "description": "Stylesheet resource contents (if available)."} ], "description": "CSS stylesheet contents." }, { "id": "CSSRule", "type": "object", "properties": [ { "name": "ruleId", "$ref": "CSSRuleId", "optional": true, "description": "The CSS rule identifier (absent for user agent stylesheet and user-specified stylesheet rules)."}, { "name": "selectorList", "$ref": "SelectorList", "description": "Rule selector data." }, { "name": "sourceURL", "type": "string", "optional": true, "description": "Parent stylesheet resource URL (for regular rules)."}, { "name": "origin", "$ref": "StyleSheetOrigin", "description": "Parent stylesheet's origin."}, { "name": "style", "$ref": "CSSStyle", "description": "Associated style declaration." }, { "name": "media", "type": "array", "items": { "$ref": "CSSMedia" }, "optional": true, "description": "Media list array (for rules involving media queries). The array enumerates media queries starting with the innermost one, going outwards." } ], "description": "CSS rule representation." }, { "id": "SourceRange", "type": "object", "properties": [ { "name": "startLine", "type": "integer", "description": "Start line of range." }, { "name": "startColumn", "type": "integer", "description": "Start column of range (inclusive)." }, { "name": "endLine", "type": "integer", "description": "End line of range" }, { "name": "endColumn", "type": "integer", "description": "End column of range (exclusive)." } ], "description": "Text range within a resource. All numbers are zero-based." }, { "id": "ShorthandEntry", "type": "object", "properties": [ { "name": "name", "type": "string", "description": "Shorthand name." }, { "name": "value", "type": "string", "description": "Shorthand value." } ] }, { "id": "CSSPropertyInfo", "type": "object", "properties": [ { "name": "name", "type": "string", "description": "Property name." }, { "name": "longhands", "type": "array", "optional": true, "items": { "type": "string" }, "description": "Longhand property names." } ] }, { "id": "CSSComputedStyleProperty", "type": "object", "properties": [ { "name": "name", "type": "string", "description": "Computed style property name." }, { "name": "value", "type": "string", "description": "Computed style property value." } ] }, { "id": "CSSStyle", "type": "object", "properties": [ { "name": "styleId", "$ref": "CSSStyleId", "optional": true, "description": "The CSS style identifier (absent for attribute styles)." }, { "name": "cssProperties", "type": "array", "items": { "$ref": "CSSProperty" }, "description": "CSS properties in the style." }, { "name": "shorthandEntries", "type": "array", "items": { "$ref": "ShorthandEntry" }, "description": "Computed values for all shorthands found in the style." }, { "name": "cssText", "type": "string", "optional": true, "description": "Style declaration text (if available)." }, { "name": "range", "$ref": "SourceRange", "optional": true, "description": "Style declaration range in the enclosing stylesheet (if available)." }, { "name": "width", "type": "string", "optional": true, "description": "The effective \"width\" property value from this style." }, { "name": "height", "type": "string", "optional": true, "description": "The effective \"height\" property value from this style." } ], "description": "CSS style representation." }, { "id": "CSSProperty", "type": "object", "properties": [ { "name": "name", "type": "string", "description": "The property name." }, { "name": "value", "type": "string", "description": "The property value." }, { "name": "priority", "type": "string", "optional": true, "description": "The property priority (implies \"\" if absent)." }, { "name": "implicit", "type": "boolean", "optional": true, "description": "Whether the property is implicit (implies false if absent)." }, { "name": "text", "type": "string", "optional": true, "description": "The full property text as specified in the style." }, { "name": "parsedOk", "type": "boolean", "optional": true, "description": "Whether the property is understood by the browser (implies true if absent)." }, { "name": "status", "type": "string", "enum": ["active", "inactive", "disabled", "style"], "optional": true, "description": "The property status: \"active\" if the property is effective in the style, \"inactive\" if the property is overridden by a same-named property in this style later on, \"disabled\" if the property is disabled by the user, \"style\" (implied if absent) if the property is reported by the browser rather than by the CSS source parser." }, { "name": "range", "$ref": "SourceRange", "optional": true, "description": "The entire property range in the enclosing style declaration (if available)." } ], "description": "CSS property declaration data." }, { "id": "CSSMedia", "type": "object", "properties": [ { "name": "text", "type": "string", "description": "Media query text." }, { "name": "source", "type": "string", "enum": ["mediaRule", "importRule", "linkedSheet", "inlineSheet"], "description": "Source of the media query: \"mediaRule\" if specified by a @media rule, \"importRule\" if specified by an @import rule, \"linkedSheet\" if specified by a \"media\" attribute in a linked stylesheet's LINK tag, \"inlineSheet\" if specified by a \"media\" attribute in an inline stylesheet's STYLE tag." }, { "name": "sourceURL", "type": "string", "optional": true, "description": "URL of the document containing the media query description." }, { "name": "range", "$ref": "SourceRange", "optional": true, "description": "The associated rule (@media or @import) header range in the enclosing stylesheet (if available)." }, { "name": "parentStyleSheetId", "$ref": "StyleSheetId", "optional": true, "description": "Identifier of the stylesheet containing this object (if exists)." } ], "description": "CSS media query descriptor." }, { "id": "SelectorProfileEntry", "type": "object", "properties": [ { "name": "selector", "type": "string", "description": "CSS selector of the corresponding rule." }, { "name": "url", "type": "string", "description": "URL of the resource containing the corresponding rule." }, { "name": "lineNumber", "type": "integer", "description": "Selector line number in the resource for the corresponding rule." }, { "name": "time", "type": "number", "description": "Total time this rule handling contributed to the browser running time during profiling (in milliseconds)." }, { "name": "hitCount", "type": "integer", "description": "Number of times this rule was considered a candidate for matching against DOM elements." }, { "name": "matchCount", "type": "integer", "description": "Number of times this rule actually matched a DOM element." } ], "description": "CSS selector profile entry." }, { "id": "SelectorProfile", "type": "object", "properties": [ { "name": "totalTime", "type": "number", "description": "Total processing time for all selectors in the profile (in milliseconds)." }, { "name": "data", "type": "array", "items": { "$ref": "SelectorProfileEntry" }, "description": "CSS selector profile entries." } ] }, { "id": "Region", "type": "object", "properties": [ { "name": "regionOverset", "type": "string", "enum": ["overset", "fit", "empty"], "description": "The \"overset\" attribute of a Named Flow." }, { "name": "nodeId", "$ref": "DOM.NodeId", "description": "The corresponding DOM node id." } ], "description": "This object represents a region that flows from a Named Flow.", "hidden": true }, { "id": "NamedFlow", "type": "object", "properties": [ { "name": "documentNodeId", "$ref": "DOM.NodeId", "description": "The document node id." }, { "name": "name", "type": "string", "description": "Named Flow identifier." }, { "name": "overset", "type": "boolean", "description": "The \"overset\" attribute of a Named Flow." }, { "name": "content", "type": "array", "items": { "$ref": "DOM.NodeId" }, "description": "An array of nodes that flow into the Named Flow." }, { "name": "regions", "type": "array", "items": { "$ref": "Region" }, "description": "An array of regions associated with the Named Flow." } ], "description": "This object represents a Named Flow.", "hidden": true }, { "id": "PlatformFontUsage", "type": "object", "properties": [ { "name": "familyName", "type": "string", "description": "Font's family name reported by platform."}, { "name": "glyphCount", "type": "number", "description": "Amount of glyphs that were rendered with this font."} ], "description": "Information about amount of glyphs that were rendered with given font." } ], "commands": [ { "name": "enable", "async": true, "description": "Enables the CSS agent for the given page. Clients should not assume that the CSS agent has been enabled until the result of this command is received." }, { "name": "disable", "description": "Disables the CSS agent for the given page." }, { "name": "getMatchedStylesForNode", "parameters": [ { "name": "nodeId", "$ref": "DOM.NodeId" }, { "name": "includePseudo", "type": "boolean", "optional": true, "description": "Whether to include pseudo styles (default: true)." }, { "name": "includeInherited", "type": "boolean", "optional": true, "description": "Whether to include inherited styles (default: true)." } ], "returns": [ { "name": "matchedCSSRules", "type": "array", "items": { "$ref": "RuleMatch" }, "optional": true, "description": "CSS rules matching this node, from all applicable stylesheets." }, { "name": "pseudoElements", "type": "array", "items": { "$ref": "PseudoIdMatches" }, "optional": true, "description": "Pseudo style matches for this node." }, { "name": "inherited", "type": "array", "items": { "$ref": "InheritedStyleEntry" }, "optional": true, "description": "A chain of inherited styles (from the immediate node parent up to the DOM tree root)." } ], "description": "Returns requested styles for a DOM node identified by nodeId." }, { "name": "getInlineStylesForNode", "parameters": [ { "name": "nodeId", "$ref": "DOM.NodeId" } ], "returns": [ { "name": "inlineStyle", "$ref": "CSSStyle", "optional": true, "description": "Inline style for the specified DOM node." }, { "name": "attributesStyle", "$ref": "CSSStyle", "optional": true, "description": "Attribute-defined element style (e.g. resulting from \"width=20 height=100%\")."} ], "description": "Returns the styles defined inline (explicitly in the \"style\" attribute and implicitly, using DOM attributes) for a DOM node identified by nodeId." }, { "name": "getComputedStyleForNode", "parameters": [ { "name": "nodeId", "$ref": "DOM.NodeId" } ], "returns": [ { "name": "computedStyle", "type": "array", "items": { "$ref": "CSSComputedStyleProperty" }, "description": "Computed style for the specified DOM node." } ], "description": "Returns the computed style for a DOM node identified by nodeId." }, { "name": "getPlatformFontsForNode", "parameters": [ { "name": "nodeId", "$ref": "DOM.NodeId" } ], "returns": [ { "name": "cssFamilyName", "type": "string", "description": "Font family name which is determined by computed style." }, { "name": "fonts", "type": "array", "items": { "$ref": "PlatformFontUsage"}, "description": "Usage statistics for every employed platform font." } ], "description": "Requests information about platform fonts which we used to render child TextNodes in the given node.", "hidden": true }, { "name": "getAllStyleSheets", "returns": [ { "name": "headers", "type": "array", "items": { "$ref": "CSSStyleSheetHeader" }, "description": "Descriptor entries for all available stylesheets." } ], "description": "Returns metainfo entries for all known stylesheets." }, { "name": "getStyleSheet", "parameters": [ { "name": "styleSheetId", "$ref": "StyleSheetId" } ], "returns": [ { "name": "styleSheet", "$ref": "CSSStyleSheetBody", "description": "Stylesheet contents for the specified styleSheetId." } ], "description": "Returns stylesheet data for the specified styleSheetId." }, { "name": "getStyleSheetText", "parameters": [ { "name": "styleSheetId", "$ref": "StyleSheetId" } ], "returns": [ { "name": "text", "type": "string", "description": "The stylesheet text." } ], "description": "Returns the current textual content and the URL for a stylesheet." }, { "name": "setStyleSheetText", "parameters": [ { "name": "styleSheetId", "$ref": "StyleSheetId" }, { "name": "text", "type": "string" } ], "description": "Sets the new stylesheet text, thereby invalidating all existing CSSStyleId's and CSSRuleId's contained by this stylesheet." }, { "name": "setStyleText", "parameters": [ { "name": "styleId", "$ref": "CSSStyleId" }, { "name": "text", "type": "string" } ], "returns": [ { "name": "style", "$ref": "CSSStyle", "description": "The resulting style after the text modification." } ], "description": "Updates the CSSStyleDeclaration text." }, { "name": "setPropertyText", "parameters": [ { "name": "styleId", "$ref": "CSSStyleId" }, { "name": "propertyIndex", "type": "integer" }, { "name": "text", "type": "string" }, { "name": "overwrite", "type": "boolean" } ], "returns": [ { "name": "style", "$ref": "CSSStyle", "description": "The resulting style after the property text modification." } ], "description": "Sets the new text for a property in the respective style, at offset propertyIndex. If overwrite is true, a property at the given offset is overwritten, otherwise inserted. text entirely replaces the property name: value." }, { "name": "toggleProperty", "parameters": [ { "name": "styleId", "$ref": "CSSStyleId" }, { "name": "propertyIndex", "type": "integer" }, { "name": "disable", "type": "boolean" } ], "returns": [ { "name": "style", "$ref": "CSSStyle", "description": "The resulting style after the property toggling." } ], "description": "Toggles the property in the respective style, at offset propertyIndex. The disable parameter denotes whether the property should be disabled (i.e. removed from the style declaration). If disable == false, the property gets put back into its original place in the style declaration." }, { "name": "setRuleSelector", "parameters": [ { "name": "ruleId", "$ref": "CSSRuleId" }, { "name": "selector", "type": "string" } ], "returns": [ { "name": "rule", "$ref": "CSSRule", "description": "The resulting rule after the selector modification." } ], "description": "Modifies the rule selector." }, { "name": "addRule", "parameters": [ { "name": "contextNodeId", "$ref": "DOM.NodeId" }, { "name": "selector", "type": "string" } ], "returns": [ { "name": "rule", "$ref": "CSSRule", "description": "The newly created rule." } ], "description": "Creates a new empty rule with the given selector in a special \"inspector\" stylesheet in the owner document of the context node." }, { "name": "getSupportedCSSProperties", "returns": [ { "name": "cssProperties", "type": "array", "items": { "$ref": "CSSPropertyInfo" }, "description": "Supported property metainfo." } ], "description": "Returns all supported CSS property names." }, { "name": "forcePseudoState", "parameters": [ { "name": "nodeId", "$ref": "DOM.NodeId", "description": "The element id for which to force the pseudo state." }, { "name": "forcedPseudoClasses", "type": "array", "items": { "type": "string", "enum": ["active", "focus", "hover", "visited"] }, "description": "Element pseudo classes to force when computing the element's style." } ], "description": "Ensures that the given node will have specified pseudo-classes whenever its style is computed by the browser." }, { "name": "getNamedFlowCollection", "parameters": [ { "name": "documentNodeId", "$ref": "DOM.NodeId", "description": "The document node id for which to get the Named Flow Collection." } ], "returns": [ { "name": "namedFlows", "type": "array", "items": { "$ref": "NamedFlow" }, "description": "An array containing the Named Flows in the document." } ], "description": "Returns the Named Flows from the document.", "hidden": true } ], "events": [ { "name": "mediaQueryResultChanged", "description": "Fires whenever a MediaQuery result changes (for example, after a browser window has been resized.) The current implementation considers only viewport-dependent media features." }, { "name": "styleSheetChanged", "parameters": [ { "name": "styleSheetId", "$ref": "StyleSheetId" } ], "description": "Fired whenever a stylesheet is changed as a result of the client operation." }, { "name": "styleSheetAdded", "parameters": [ { "name": "header", "$ref": "CSSStyleSheetHeader", "description": "Added stylesheet metainfo." } ], "description": "Fired whenever an active document stylesheet is added." }, { "name": "styleSheetRemoved", "parameters": [ { "name": "styleSheetId", "$ref": "StyleSheetId", "description": "Identifier of the removed stylesheet." } ], "description": "Fired whenever an active document stylesheet is removed." }, { "name": "namedFlowCreated", "parameters": [ { "name": "namedFlow", "$ref": "NamedFlow", "description": "The new Named Flow." } ], "description": "Fires when a Named Flow is created.", "hidden": true }, { "name": "namedFlowRemoved", "parameters": [ { "name": "documentNodeId", "$ref": "DOM.NodeId", "description": "The document node id." }, { "name": "flowName", "type": "string", "description": "Identifier of the removed Named Flow." } ], "description": "Fires when a Named Flow is removed: has no associated content nodes and regions.", "hidden": true }, { "name": "regionLayoutUpdated", "parameters": [ { "name": "namedFlow", "$ref": "NamedFlow", "description": "The Named Flow whose layout may have changed." } ], "description": "Fires when a Named Flow's layout may have changed.", "hidden": true }, { "name": "regionOversetChanged", "parameters": [ { "name": "namedFlow", "$ref": "NamedFlow", "description": "The Named Flow containing the regions whose regionOverset values changed." } ], "description": "Fires if any of the regionOverset values changed in a Named Flow's region chain.", "hidden": true } ] }, { "domain": "Timeline", "description": "Timeline provides its clients with instrumentation records that are generated during the page runtime. Timeline instrumentation can be started and stopped using corresponding commands. While timeline is started, it is generating timeline event records.", "types": [ { "id": "DOMCounters", "type": "object", "properties": [ { "name": "documents", "type": "integer" }, { "name": "nodes", "type": "integer" }, { "name": "jsEventListeners", "type": "integer" } ], "description": "Current values of DOM counters.", "hidden": true }, { "id": "TimelineEvent", "type": "object", "properties": [ { "name": "type", "type": "string", "description": "Event type." }, { "name": "thread", "type": "string", "optional": true, "description": "If present, identifies the thread that produced the event.", "hidden": true }, { "name": "data", "type": "object", "description": "Event data." }, { "name": "children", "type": "array", "optional": true, "items": { "$ref": "TimelineEvent" }, "description": "Nested records." }, { "name": "counters", "$ref": "DOMCounters", "optional": true, "hidden": true, "description": "Current values of DOM counters." }, { "name": "usedHeapSize", "type": "integer", "optional": true, "hidden": true, "description": "Current size of JS heap." }, { "name": "nativeHeapStatistics", "type": "object", "optional": true, "hidden": true, "description": "Native heap statistics." } ], "description": "Timeline record contains information about the recorded activity." } ], "commands": [ { "name": "enable", "description": "Enables timeline. After this call, timeline can be started from within the page (for example upon console.timeline)." }, { "name": "disable", "description": "Disables timeline." }, { "name": "start", "parameters": [ { "name": "maxCallStackDepth", "optional": true, "type": "integer", "description": "Samples JavaScript stack traces up to maxCallStackDepth, defaults to 5." }, { "name": "bufferEvents", "optional": true, "type": "boolean", "hidden": true, "description": "Whether instrumentation events should be buffered and returned upon stop call." }, { "name": "includeDomCounters", "optional": true, "type": "boolean", "hidden": true, "description": "Whether DOM counters data should be included into timeline events." }, { "name": "includeNativeMemoryStatistics", "optional": true, "type": "boolean", "hidden": true, "description": "Whether native memory usage statistics should be reported as part of timeline events." } ], "description": "Starts capturing instrumentation events." }, { "name": "stop", "description": "Stops capturing instrumentation events.", "returns": [ { "name": "events", "type": "array", "items": { "$ref": "TimelineEvent" }, "optional": true, "hidden": true, "description": "Timeline event record data." } ] } ], "events": [ { "name": "eventRecorded", "parameters": [ { "name": "record", "$ref": "TimelineEvent", "description": "Timeline event record data." } ], "description": "Fired for every instrumentation event while timeline is started." }, { "name": "started", "parameters": [ { "name": "consoleTimeline", "type": "boolean", "optional": true, "description": "If specified, identifies that timeline was started using console.timeline() call." } ], "description": "Fired when timeline is started.", "hidden": true }, { "name": "stopped", "parameters": [ { "name": "consoleTimeline", "type": "boolean", "optional": true, "description": "If specified, identifies that timeline was started using console.timeline() call." } ], "description": "Fired when timeline is stopped.", "hidden": true } ] }, { "domain": "Debugger", "description": "Debugger domain exposes JavaScript debugging capabilities. It allows setting and removing breakpoints, stepping through execution, exploring stack traces, etc.", "types": [ { "id": "BreakpointId", "type": "string", "description": "Breakpoint identifier." }, { "id": "ScriptId", "type": "string", "description": "Unique script identifier." }, { "id": "CallFrameId", "type": "string", "description": "Call frame identifier." }, { "id": "Location", "type": "object", "properties": [ { "name": "scriptId", "$ref": "ScriptId", "description": "Script identifier as reported in the Debugger.scriptParsed." }, { "name": "lineNumber", "type": "integer", "description": "Line number in the script (0-based)." }, { "name": "columnNumber", "type": "integer", "optional": true, "description": "Column number in the script (0-based)." } ], "description": "Location in the source code." }, { "id": "FunctionDetails", "hidden": true, "type": "object", "properties": [ { "name": "location", "$ref": "Location", "description": "Location of the function." }, { "name": "name", "type": "string", "optional": true, "description": "Name of the function. Not present for anonymous functions." }, { "name": "displayName", "type": "string", "optional": true, "description": "Display name of the function(specified in 'displayName' property on the function object)." }, { "name": "inferredName", "type": "string", "optional": true, "description": "Name of the function inferred from its initial assignment." }, { "name": "scopeChain", "type": "array", "optional": true, "items": { "$ref": "Scope" }, "description": "Scope chain for this closure." } ], "description": "Information about the function." }, { "id": "CallFrame", "type": "object", "properties": [ { "name": "callFrameId", "$ref": "CallFrameId", "description": "Call frame identifier. This identifier is only valid while the virtual machine is paused." }, { "name": "functionName", "type": "string", "description": "Name of the JavaScript function called on this call frame." }, { "name": "location", "$ref": "Location", "description": "Location in the source code." }, { "name": "scopeChain", "type": "array", "items": { "$ref": "Scope" }, "description": "Scope chain for this call frame." }, { "name": "this", "$ref": "Runtime.RemoteObject", "description": "this object for this call frame." } ], "description": "JavaScript call frame. Array of call frames form the call stack." }, { "id": "Scope", "type": "object", "properties": [ { "name": "type", "type": "string", "enum": ["global", "local", "with", "closure", "catch"], "description": "Scope type." }, { "name": "object", "$ref": "Runtime.RemoteObject", "description": "Object representing the scope. For global and with scopes it represents the actual object; for the rest of the scopes, it is artificial transient object enumerating scope variables as its properties." } ], "description": "Scope description." }, { "id": "SetScriptSourceError", "type": "object", "properties": [ { "name": "compileError", "optional": true, "type": "object", "properties": [ { "name": "message", "type": "string", "description": "Compiler error message" }, { "name": "lineNumber", "type": "integer", "description": "Compile error line number (1-based)" }, { "name": "columnNumber", "type": "integer", "description": "Compile error column number (1-based)" } ] } ], "description": "Error data for setScriptSource command. compileError is a case type for uncompilable script source error.", "hidden": true } ], "commands": [ { "name": "enable", "description": "Enables debugger for the given page. Clients should not assume that the debugging has been enabled until the result for this command is received." }, { "name": "disable", "description": "Disables debugger for given page." }, { "name": "setBreakpointsActive", "parameters": [ { "name": "active", "type": "boolean", "description": "New value for breakpoints active state." } ], "description": "Activates / deactivates all breakpoints on the page." }, { "name": "setSkipAllPauses", "hidden": true, "parameters": [ { "name": "skipped", "type": "boolean", "description": "New value for skip pauses state." }, { "name": "untilReload", "type": "boolean", "optional": true, "description": "Whether page reload should set skipped to false." } ], "description": "Makes page not interrupt on any pauses (breakpoint, exception, dom exception etc)." }, { "name": "setBreakpointByUrl", "parameters": [ { "name": "lineNumber", "type": "integer", "description": "Line number to set breakpoint at." }, { "name": "url", "type": "string", "optional": true, "description": "URL of the resources to set breakpoint on." }, { "name": "urlRegex", "type": "string", "optional": true, "description": "Regex pattern for the URLs of the resources to set breakpoints on. Either url or urlRegex must be specified." }, { "name": "columnNumber", "type": "integer", "optional": true, "description": "Offset in the line to set breakpoint at." }, { "name": "condition", "type": "string", "optional": true, "description": "Expression to use as a breakpoint condition. When specified, debugger will only stop on the breakpoint if this expression evaluates to true." }, { "name": "isAntibreakpoint", "type": "boolean", "optional": true, "hidden": true, "description": "Creates pseudo-breakpoint that prevents debugger from pausing on exception at this location." } ], "returns": [ { "name": "breakpointId", "$ref": "BreakpointId", "description": "Id of the created breakpoint for further reference." }, { "name": "locations", "type": "array", "items": { "$ref": "Location"}, "description": "List of the locations this breakpoint resolved into upon addition." } ], "description": "Sets JavaScript breakpoint at given location specified either by URL or URL regex. Once this command is issued, all existing parsed scripts will have breakpoints resolved and returned in locations property. Further matching script parsing will result in subsequent breakpointResolved events issued. This logical breakpoint will survive page reloads." }, { "name": "setBreakpoint", "parameters": [ { "name": "location", "$ref": "Location", "description": "Location to set breakpoint in." }, { "name": "condition", "type": "string", "optional": true, "description": "Expression to use as a breakpoint condition. When specified, debugger will only stop on the breakpoint if this expression evaluates to true." } ], "returns": [ { "name": "breakpointId", "$ref": "BreakpointId", "description": "Id of the created breakpoint for further reference." }, { "name": "actualLocation", "$ref": "Location", "description": "Location this breakpoint resolved into." } ], "description": "Sets JavaScript breakpoint at a given location." }, { "name": "removeBreakpoint", "parameters": [ { "name": "breakpointId", "$ref": "BreakpointId" } ], "description": "Removes JavaScript breakpoint." }, { "name": "continueToLocation", "parameters": [ { "name": "location", "$ref": "Location", "description": "Location to continue to." }, { "name": "interstatementLocation", "type": "boolean", "optional": true, "hidden": true, "description": "Allows breakpoints at the intemediate positions inside statements." } ], "description": "Continues execution until specific location is reached." }, { "name": "stepOver", "description": "Steps over the statement." }, { "name": "stepInto", "description": "Steps into the function call." }, { "name": "stepOut", "description": "Steps out of the function call." }, { "name": "pause", "description": "Stops on the next JavaScript statement." }, { "name": "resume", "description": "Resumes JavaScript execution." }, { "name": "searchInContent", "parameters": [ { "name": "scriptId", "$ref": "ScriptId", "description": "Id of the script to search in." }, { "name": "query", "type": "string", "description": "String to search for." }, { "name": "caseSensitive", "type": "boolean", "optional": true, "description": "If true, search is case sensitive." }, { "name": "isRegex", "type": "boolean", "optional": true, "description": "If true, treats string parameter as regex." } ], "returns": [ { "name": "result", "type": "array", "items": { "$ref": "Page.SearchMatch" }, "description": "List of search matches." } ], "description": "Searches for given string in script content." }, { "name": "canSetScriptSource", "returns": [ { "name": "result", "type": "boolean", "description": "True if setScriptSource is supported." } ], "description": "Always returns true." }, { "name": "setScriptSource", "parameters": [ { "name": "scriptId", "$ref": "ScriptId", "description": "Id of the script to edit." }, { "name": "scriptSource", "type": "string", "description": "New content of the script." }, { "name": "preview", "type": "boolean", "optional": true, "description": " If true the change will not actually be applied. Preview mode may be used to get result description without actually modifying the code.", "hidden": true } ], "returns": [ { "name": "callFrames", "type": "array", "optional": true, "items": { "$ref": "CallFrame"}, "description": "New stack trace in case editing has happened while VM was stopped." }, { "name": "result", "type": "object", "optional": true, "description": "VM-specific description of the changes applied.", "hidden": true } ], "error": { "$ref": "SetScriptSourceError" }, "description": "Edits JavaScript source live." }, { "name": "restartFrame", "parameters": [ { "name": "callFrameId", "$ref": "CallFrameId", "description": "Call frame identifier to evaluate on." } ], "returns": [ { "name": "callFrames", "type": "array", "items": { "$ref": "CallFrame"}, "description": "New stack trace." }, { "name": "result", "type": "object", "description": "VM-specific description.", "hidden": true } ], "hidden": true, "description": "Restarts particular call frame from the beginning." }, { "name": "getScriptSource", "parameters": [ { "name": "scriptId", "$ref": "ScriptId", "description": "Id of the script to get source for." } ], "returns": [ { "name": "scriptSource", "type": "string", "description": "Script source." } ], "description": "Returns source for the script with given id." }, { "name": "getFunctionDetails", "hidden": true, "parameters": [ { "name": "functionId", "$ref": "Runtime.RemoteObjectId", "description": "Id of the function to get location for." } ], "returns": [ { "name": "details", "$ref": "FunctionDetails", "description": "Information about the function." } ], "description": "Returns detailed informtation on given function." }, { "name": "setPauseOnExceptions", "parameters": [ { "name": "state", "type": "string", "enum": ["none", "uncaught", "all"], "description": "Pause on exceptions mode." } ], "description": "Defines pause on exceptions state. Can be set to stop on all exceptions, uncaught exceptions or no exceptions. Initial pause on exceptions state is none." }, { "name": "evaluateOnCallFrame", "parameters": [ { "name": "callFrameId", "$ref": "CallFrameId", "description": "Call frame identifier to evaluate on." }, { "name": "expression", "type": "string", "description": "Expression to evaluate." }, { "name": "objectGroup", "type": "string", "optional": true, "description": "String object group name to put result into (allows rapid releasing resulting object handles using releaseObjectGroup)." }, { "name": "includeCommandLineAPI", "type": "boolean", "optional": true, "description": "Specifies whether command line API should be available to the evaluated expression, defaults to false.", "hidden": true }, { "name": "doNotPauseOnExceptionsAndMuteConsole", "type": "boolean", "optional": true, "description": "Specifies whether evaluation should stop on exceptions and mute console. Overrides setPauseOnException state.", "hidden": true }, { "name": "returnByValue", "type": "boolean", "optional": true, "description": "Whether the result is expected to be a JSON object that should be sent by value." }, { "name": "generatePreview", "type": "boolean", "optional": true, "hidden": true, "description": "Whether preview should be generated for the result." } ], "returns": [ { "name": "result", "$ref": "Runtime.RemoteObject", "description": "Object wrapper for the evaluation result." }, { "name": "wasThrown", "type": "boolean", "optional": true, "description": "True if the result was thrown during the evaluation." } ], "description": "Evaluates expression on a given call frame." }, { "name": "compileScript", "hidden": true, "parameters": [ { "name": "expression", "type": "string", "description": "Expression to compile." }, { "name": "sourceURL", "type": "string", "description": "Source url to be set for the script." } ], "returns": [ { "name": "scriptId", "$ref": "ScriptId", "optional": true, "description": "Id of the script." }, { "name": "syntaxErrorMessage", "type": "string", "optional": true, "description": "Syntax error message if compilation failed." } ], "description": "Compiles expression." }, { "name": "runScript", "hidden": true, "parameters": [ { "name": "scriptId", "$ref": "ScriptId", "description": "Id of the script to run." }, { "name": "contextId", "$ref": "Runtime.ExecutionContextId", "optional": true, "description": "Specifies in which isolated context to perform script run. Each content script lives in an isolated context and this parameter may be used to specify one of those contexts. If the parameter is omitted or 0 the evaluation will be performed in the context of the inspected page." }, { "name": "objectGroup", "type": "string", "optional": true, "description": "Symbolic group name that can be used to release multiple objects." }, { "name": "doNotPauseOnExceptionsAndMuteConsole", "type": "boolean", "optional": true, "description": "Specifies whether script run should stop on exceptions and mute console. Overrides setPauseOnException state." } ], "returns": [ { "name": "result", "$ref": "Runtime.RemoteObject", "description": "Run result." }, { "name": "wasThrown", "type": "boolean", "optional": true, "description": "True if the result was thrown during the script run." } ], "description": "Runs script with given id in a given context." }, { "name": "setOverlayMessage", "parameters": [ { "name": "message", "type": "string", "optional": true, "description": "Overlay message to display when paused in debugger." } ], "hidden": true, "description": "Sets overlay message." }, { "name": "setVariableValue", "parameters": [ { "name": "scopeNumber", "type": "integer", "description": "0-based number of scope as was listed in scope chain. Only 'local', 'closure' and 'catch' scope types are allowed. Other scopes could be manipulated manually." }, { "name": "variableName", "type": "string", "description": "Variable name." }, { "name": "newValue", "$ref": "Runtime.CallArgument", "description": "New variable value." }, { "name": "callFrameId", "$ref": "CallFrameId", "optional": true, "description": "Id of callframe that holds variable." }, { "name": "functionObjectId", "$ref": "Runtime.RemoteObjectId", "optional": true, "description": "Object id of closure (function) that holds variable." } ], "hidden": true, "description": "Changes value of variable in a callframe or a closure. Either callframe or function must be specified. Object-based scopes are not supported and must be mutated manually." }, { "name": "getStepInPositions", "parameters": [ { "name": "callFrameId", "$ref": "CallFrameId", "description": "Id of a call frame where the current statement should be analized" } ], "returns": [ { "name": "stepInPositions", "type": "array", "items": { "$ref": "Location" }, "optional": true, "description": "experimental" } ], "hidden": true, "description": "Lists all positions where step-in is possible for a current statement in a specified call frame" }, { "name": "getBacktrace", "returns": [ { "name": "callFrames", "type": "array", "items": { "$ref": "CallFrame"}, "description": "Call stack the virtual machine stopped on." } ], "hidden": true, "description": "Returns call stack including variables changed since VM was paused. VM must be paused." }, { "name": "skipStackFrames", "parameters": [ { "name": "script", "optional": true, "type": "string", "description": "Regular expression defining the scripts to ignore while stepping." } ], "hidden": true, "description": "Makes backend skip steps in the sources with names matching given pattern. VM will try leave blacklisted scripts by performing 'step in' several times, finally resorting to 'step out' if unsuccessful." } ], "events": [ { "name": "globalObjectCleared", "description": "Called when global has been cleared and debugger client should reset its state. Happens upon navigation or reload." }, { "name": "scriptParsed", "parameters": [ { "name": "scriptId", "$ref": "ScriptId", "description": "Identifier of the script parsed." }, { "name": "url", "type": "string", "description": "URL or name of the script parsed (if any)." }, { "name": "startLine", "type": "integer", "description": "Line offset of the script within the resource with given URL (for script tags)." }, { "name": "startColumn", "type": "integer", "description": "Column offset of the script within the resource with given URL." }, { "name": "endLine", "type": "integer", "description": "Last line of the script." }, { "name": "endColumn", "type": "integer", "description": "Length of the last line of the script." }, { "name": "isContentScript", "type": "boolean", "optional": true, "description": "Determines whether this script is a user extension script." }, { "name": "sourceMapURL", "type": "string", "optional": true, "description": "URL of source map associated with script (if any)." }, { "name": "hasSourceURL", "type": "boolean", "optional": true, "description": "True, if this script has sourceURL.", "hidden": true } ], "description": "Fired when virtual machine parses script. This event is also fired for all known and uncollected scripts upon enabling debugger." }, { "name": "scriptFailedToParse", "parameters": [ { "name": "url", "type": "string", "description": "URL of the script that failed to parse." }, { "name": "scriptSource", "type": "string", "description": "Source text of the script that failed to parse." }, { "name": "startLine", "type": "integer", "description": "Line offset of the script within the resource." }, { "name": "errorLine", "type": "integer", "description": "Line with error." }, { "name": "errorMessage", "type": "string", "description": "Parse error message." } ], "description": "Fired when virtual machine fails to parse the script." }, { "name": "breakpointResolved", "parameters": [ { "name": "breakpointId", "$ref": "BreakpointId", "description": "Breakpoint unique identifier." }, { "name": "location", "$ref": "Location", "description": "Actual breakpoint location." } ], "description": "Fired when breakpoint is resolved to an actual script and location." }, { "name": "paused", "parameters": [ { "name": "callFrames", "type": "array", "items": { "$ref": "CallFrame" }, "description": "Call stack the virtual machine stopped on." }, { "name": "reason", "type": "string", "enum": [ "XHR", "DOM", "EventListener", "exception", "assert", "CSPViolation", "debugCommand", "other" ], "description": "Pause reason." }, { "name": "data", "type": "object", "optional": true, "description": "Object containing break-specific auxiliary properties." }, { "name": "hitBreakpoints", "type": "array", "optional": true, "items": { "type": "string" }, "description": "Hit breakpoints IDs", "hidden": true } ], "description": "Fired when the virtual machine stopped on breakpoint or exception or any other stop criteria." }, { "name": "resumed", "description": "Fired when the virtual machine resumed execution." } ] }, { "domain": "DOMDebugger", "description": "DOM debugging allows setting breakpoints on particular DOM operations and events. JavaScript execution will stop on these operations as if there was a regular breakpoint set.", "types": [ { "id": "DOMBreakpointType", "type": "string", "enum": ["subtree-modified", "attribute-modified", "node-removed"], "description": "DOM breakpoint type." } ], "commands": [ { "name": "setDOMBreakpoint", "parameters": [ { "name": "nodeId", "$ref": "DOM.NodeId", "description": "Identifier of the node to set breakpoint on." }, { "name": "type", "$ref": "DOMBreakpointType", "description": "Type of the operation to stop upon." } ], "description": "Sets breakpoint on particular operation with DOM." }, { "name": "removeDOMBreakpoint", "parameters": [ { "name": "nodeId", "$ref": "DOM.NodeId", "description": "Identifier of the node to remove breakpoint from." }, { "name": "type", "$ref": "DOMBreakpointType", "description": "Type of the breakpoint to remove." } ], "description": "Removes DOM breakpoint that was set using setDOMBreakpoint." }, { "name": "setEventListenerBreakpoint", "parameters": [ { "name": "eventName", "type": "string", "description": "DOM Event name to stop on (any DOM event will do)." } ], "description": "Sets breakpoint on particular DOM event." }, { "name": "removeEventListenerBreakpoint", "parameters": [ { "name": "eventName", "type": "string", "description": "Event name." } ], "description": "Removes breakpoint on particular DOM event." }, { "name": "setInstrumentationBreakpoint", "parameters": [ { "name": "eventName", "type": "string", "description": "Instrumentation name to stop on." } ], "description": "Sets breakpoint on particular native event.", "hidden": true }, { "name": "removeInstrumentationBreakpoint", "parameters": [ { "name": "eventName", "type": "string", "description": "Instrumentation name to stop on." } ], "description": "Removes breakpoint on particular native event.", "hidden": true }, { "name": "setXHRBreakpoint", "parameters": [ { "name": "url", "type": "string", "description": "Resource URL substring. All XHRs having this substring in the URL will get stopped upon." } ], "description": "Sets breakpoint on XMLHttpRequest." }, { "name": "removeXHRBreakpoint", "parameters": [ { "name": "url", "type": "string", "description": "Resource URL substring." } ], "description": "Removes breakpoint from XMLHttpRequest." } ] }, { "domain": "Profiler", "hidden": true, "types": [ { "id": "ProfileHeader", "type": "object", "description": "Profile header.", "properties": [ { "name": "title", "type": "string", "description": "Profile title." }, { "name": "uid", "type": "integer", "description": "Unique identifier of the profile." } ] }, { "id": "CPUProfileNode", "type": "object", "description": "CPU Profile node. Holds callsite information, execution statistics and child nodes.", "properties": [ { "name": "functionName", "type": "string", "description": "Function name." }, { "name": "scriptId", "$ref": "Debugger.ScriptId", "description": "Script identifier." }, { "name": "url", "type": "string", "description": "URL." }, { "name": "lineNumber", "type": "integer", "description": "Line number." }, { "name": "hitCount", "type": "integer", "description": "Number of samples where this node was on top of the call stack." }, { "name": "callUID", "type": "number", "description": "Call UID." }, { "name": "children", "type": "array", "items": { "$ref": "CPUProfileNode" }, "description": "Child nodes." }, { "name": "deoptReason", "type": "string", "description": "The reason of being not optimized. The function may be deoptimized or marked as don't optimize."}, { "name": "id", "optional": true, "type": "integer", "description": "Unique id of the node." } ] }, { "id": "CPUProfile", "type": "object", "description": "Profile.", "properties": [ { "name": "head", "$ref": "CPUProfileNode" }, { "name": "startTime", "type": "number", "description": "Profiling start time in seconds." }, { "name": "endTime", "type": "number", "description": "Profiling end time in seconds." }, { "name": "samples", "optional": true, "type": "array", "items": { "type": "integer" }, "description": "Ids of samples top nodes." } ] }, { "id": "HeapSnapshotObjectId", "type": "string", "description": "Heap snashot object id." } ], "commands": [ { "name": "enable" }, { "name": "disable" }, { "name": "setSamplingInterval", "parameters": [ { "name": "interval", "type": "integer", "description": "New sampling interval in microseconds." } ], "description": "Changes CPU profiler sampling interval. Must be called before CPU profiles recording started." }, { "name": "start" }, { "name": "stop", "returns": [ { "name": "header", "$ref": "ProfileHeader", "description": "The header of the recorded profile."} ] }, { "name": "getProfileHeaders", "returns": [ { "name": "headers", "type": "array", "items": { "$ref": "ProfileHeader"} } ] }, { "name": "getCPUProfile", "parameters": [ { "name": "uid", "type": "integer" } ], "returns": [ { "name": "profile", "$ref": "CPUProfile" } ] }, { "name": "removeProfile", "parameters": [ { "name": "type", "type": "string" }, { "name": "uid", "type": "integer" } ] }, { "name": "clearProfiles" } ], "events": [ { "name": "addProfileHeader", "parameters": [ { "name": "header", "$ref": "ProfileHeader" } ] }, { "name": "setRecordingProfile", "parameters": [ { "name": "isProfiling", "type": "boolean" } ] }, { "name": "resetProfiles" } ] }, { "domain": "HeapProfiler", "hidden": true, "types": [ { "id": "ProfileHeader", "type": "object", "description": "Profile header.", "properties": [ { "name": "title", "type": "string", "description": "Profile title." }, { "name": "uid", "type": "integer", "description": "Unique identifier of the profile." }, { "name": "maxJSObjectId", "type": "integer", "optional": true, "description": "Last seen JS object Id." } ] }, { "id": "HeapSnapshotObjectId", "type": "string", "description": "Heap snashot object id." } ], "commands": [ { "name": "getProfileHeaders", "returns": [ { "name": "headers", "type": "array", "items": { "$ref": "ProfileHeader"} } ] }, { "name": "startTrackingHeapObjects" }, { "name": "stopTrackingHeapObjects" }, { "name": "getHeapSnapshot", "parameters": [ { "name": "uid", "type": "integer" } ] }, { "name": "removeProfile", "parameters": [ { "name": "uid", "type": "integer" } ] }, { "name": "clearProfiles" }, { "name": "takeHeapSnapshot", "parameters": [ { "name": "reportProgress", "type": "boolean", "optional": true, "description": "If true 'reportHeapSnapshotProgress' events will be generated while snapshot is being taken." } ] }, { "name": "collectGarbage" }, { "name": "getObjectByHeapObjectId", "parameters": [ { "name": "objectId", "$ref": "HeapSnapshotObjectId" }, { "name": "objectGroup", "type": "string", "optional": true, "description": "Symbolic group name that can be used to release multiple objects." } ], "returns": [ { "name": "result", "$ref": "Runtime.RemoteObject", "description": "Evaluation result." } ] }, { "name": "getHeapObjectId", "parameters": [ { "name": "objectId", "$ref": "Runtime.RemoteObjectId", "description": "Identifier of the object to get heap object id for." } ], "returns": [ { "name": "heapSnapshotObjectId", "$ref": "HeapSnapshotObjectId", "description": "Id of the heap snapshot object corresponding to the passed remote object id." } ] } ], "events": [ { "name": "addProfileHeader", "parameters": [ { "name": "header", "$ref": "ProfileHeader" } ] }, { "name": "addHeapSnapshotChunk", "parameters": [ { "name": "uid", "type": "integer" }, { "name": "chunk", "type": "string" } ] }, { "name": "finishHeapSnapshot", "parameters": [ { "name": "uid", "type": "integer" } ] }, { "name": "resetProfiles" }, { "name": "reportHeapSnapshotProgress", "parameters": [ { "name": "done", "type": "integer" }, { "name": "total", "type": "integer" } ] }, { "name": "lastSeenObjectId", "description": "If heap objects tracking has been started then backend regulary sends a current value for last seen object id and corresponding timestamp. If the were changes in the heap since last event then one or more heapStatsUpdate events will be sent before a new lastSeenObjectId event.", "parameters": [ { "name": "lastSeenObjectId", "type": "integer" }, { "name": "timestamp", "type": "number" } ] }, { "name": "heapStatsUpdate", "description": "If heap objects tracking has been started then backend may send update for one or more fragments", "parameters": [ { "name": "statsUpdate", "type": "array", "items": { "type": "integer" }, "description": "An array of triplets. Each triplet describes a fragment. The first integer is the fragment index, the second integer is a total count of objects for the fragment, the third integer is a total size of the objects for the fragment."} ] } ] }, { "domain": "Worker", "hidden": true, "types": [], "commands": [ { "name": "enable" }, { "name": "disable" }, { "name": "sendMessageToWorker", "parameters": [ { "name": "workerId", "type": "integer" }, { "name": "message", "type": "object" } ] }, { "name": "canInspectWorkers", "description": "Tells whether browser supports workers inspection.", "returns": [ { "name": "result", "type": "boolean", "description": "True if browser has workers support." } ] }, { "name": "connectToWorker", "parameters": [ { "name": "workerId", "type": "integer" } ] }, { "name": "disconnectFromWorker", "parameters": [ { "name": "workerId", "type": "integer" } ] }, { "name": "setAutoconnectToWorkers", "parameters": [ { "name": "value", "type": "boolean" } ] } ], "events": [ { "name": "workerCreated", "parameters": [ { "name": "workerId", "type": "integer" }, { "name": "url", "type": "string" }, { "name": "inspectorConnected", "type": "boolean" } ] }, { "name": "workerTerminated", "parameters": [ { "name": "workerId", "type": "integer" } ] }, { "name": "dispatchMessageFromWorker", "parameters": [ { "name": "workerId", "type": "integer" }, { "name": "message", "type": "object" } ] }, { "name": "disconnectedFromWorker" } ] }, { "domain": "Canvas", "hidden": true, "types": [ { "id": "ResourceId", "type": "string", "description": "Unique resource identifier." }, { "id": "ResourceStateDescriptor", "type": "object", "description": "Resource state descriptor.", "properties": [ { "name": "name", "type": "string", "description": "State name." }, { "name": "enumValueForName", "type": "string", "optional": true, "description": "String representation of the enum value, if name stands for an enum." }, { "name": "value", "$ref": "CallArgument", "optional": true, "description": "The value associated with the particular state." }, { "name": "values", "type": "array", "items": { "$ref": "ResourceStateDescriptor" }, "optional": true, "description": "Array of values associated with the particular state. Either value or values will be specified." }, { "name": "isArray", "type": "boolean", "optional": true, "description": "True iff the given values items stand for an array rather than a list of grouped states." } ] }, { "id": "ResourceState", "type": "object", "description": "Resource state.", "properties": [ { "name": "id", "$ref": "ResourceId" }, { "name": "traceLogId", "$ref": "TraceLogId" }, { "name": "descriptors", "type": "array", "items": { "$ref": "ResourceStateDescriptor" }, "optional": true, "description": "Describes current Resource state." }, { "name": "imageURL", "type": "string", "optional": true, "description": "Screenshot image data URL." } ] }, { "id": "CallArgument", "type": "object", "properties": [ { "name": "description", "type": "string", "description": "String representation of the object." }, { "name": "enumName", "type": "string", "optional": true, "description": "Enum name, if any, that stands for the value (for example, a WebGL enum name)." }, { "name": "resourceId", "$ref": "ResourceId", "optional": true, "description": "Resource identifier. Specified for Resource objects only." }, { "name": "type", "type": "string", "optional": true, "enum": ["object", "function", "undefined", "string", "number", "boolean"], "description": "Object type. Specified for non Resource objects only." }, { "name": "subtype", "type": "string", "optional": true, "enum": ["array", "null", "node", "regexp", "date"], "description": "Object subtype hint. Specified for object type values only." }, { "name": "remoteObject", "$ref": "Runtime.RemoteObject", "optional": true, "description": "The RemoteObject, if requested." } ] }, { "id": "Call", "type": "object", "properties": [ { "name": "contextId", "$ref": "ResourceId" }, { "name": "functionName", "type": "string", "optional": true }, { "name": "arguments", "type": "array", "items": { "$ref": "CallArgument" }, "optional": true }, { "name": "result", "$ref": "CallArgument", "optional": true }, { "name": "isDrawingCall", "type": "boolean", "optional": true }, { "name": "isFrameEndCall", "type": "boolean", "optional": true }, { "name": "property", "type": "string", "optional": true }, { "name": "value", "$ref": "CallArgument", "optional": true }, { "name": "sourceURL", "type": "string", "optional": true }, { "name": "lineNumber", "type": "integer", "optional": true }, { "name": "columnNumber", "type": "integer", "optional": true } ] }, { "id": "TraceLogId", "type": "string", "description": "Unique trace log identifier." }, { "id": "TraceLog", "type": "object", "properties": [ { "name": "id", "$ref": "TraceLogId" }, { "name": "calls", "type": "array", "items": { "$ref": "Call" } }, { "name": "contexts", "type": "array", "items": { "$ref": "CallArgument" } }, { "name": "startOffset", "type": "integer" }, { "name": "alive", "type": "boolean" }, { "name": "totalAvailableCalls", "type": "number" } ] } ], "commands": [ { "name": "enable", "description": "Enables Canvas inspection." }, { "name": "disable", "description": "Disables Canvas inspection." }, { "name": "dropTraceLog", "parameters": [ { "name": "traceLogId", "$ref": "TraceLogId" } ] }, { "name": "hasUninstrumentedCanvases", "returns": [ { "name": "result", "type": "boolean" } ], "description": "Checks if there is any uninstrumented canvas in the inspected page." }, { "name": "captureFrame", "parameters": [ { "name": "frameId", "$ref": "Page.FrameId", "optional": true, "description": "Identifier of the frame containing document whose canvases are to be captured. If omitted, main frame is assumed." } ], "returns": [ { "name": "traceLogId", "$ref": "TraceLogId", "description": "Identifier of the trace log containing captured canvas calls." } ], "description": "Starts (or continues) a canvas frame capturing which will be stopped automatically after the next frame is prepared." }, { "name": "startCapturing", "parameters": [ { "name": "frameId", "$ref": "Page.FrameId", "optional": true, "description": "Identifier of the frame containing document whose canvases are to be captured. If omitted, main frame is assumed." } ], "returns": [ { "name": "traceLogId", "$ref": "TraceLogId", "description": "Identifier of the trace log containing captured canvas calls." } ], "description": "Starts (or continues) consecutive canvas frames capturing. The capturing is stopped by the corresponding stopCapturing command." }, { "name": "stopCapturing", "parameters": [ { "name": "traceLogId", "$ref": "TraceLogId" } ] }, { "name": "getTraceLog", "parameters": [ { "name": "traceLogId", "$ref": "TraceLogId" }, { "name": "startOffset", "type": "integer", "optional": true }, { "name": "maxLength", "type": "integer", "optional": true } ], "returns": [ { "name": "traceLog", "$ref": "TraceLog" } ] }, { "name": "replayTraceLog", "parameters": [ { "name": "traceLogId", "$ref": "TraceLogId" }, { "name": "stepNo", "type": "integer", "description": "Last call index in the trace log to replay (zero based)." } ], "returns": [ { "name": "resourceState", "$ref": "ResourceState" }, { "name": "replayTime", "type": "number", "description": "Replay time (in milliseconds)." } ] }, { "name": "getResourceState", "parameters": [ { "name": "traceLogId", "$ref": "TraceLogId" }, { "name": "resourceId", "$ref": "ResourceId" } ], "returns": [ { "name": "resourceState", "$ref": "ResourceState" } ] }, { "name": "evaluateTraceLogCallArgument", "parameters": [ { "name": "traceLogId", "$ref": "TraceLogId" }, { "name": "callIndex", "type": "integer", "description": "Index of the call to evaluate on (zero based)." }, { "name": "argumentIndex", "type": "integer", "description": "Index of the argument to evaluate (zero based). Provide -1 to evaluate call result." }, { "name": "objectGroup", "type": "string", "optional": true, "description": "String object group name to put result into (allows rapid releasing resulting object handles using Runtime.releaseObjectGroup)." } ], "returns": [ { "name": "result", "$ref": "Runtime.RemoteObject", "optional": true, "description": "Object wrapper for the evaluation result." }, { "name": "resourceState", "$ref": "ResourceState", "optional": true, "description": "State of the Resource object." } ], "description": "Evaluates a given trace call argument or its result." } ], "events": [ { "name": "contextCreated", "parameters": [ { "name": "frameId", "$ref": "Page.FrameId", "description": "Identifier of the frame containing a canvas with a context." } ], "description": "Fired when a canvas context has been created in the given frame. The context may not be instrumented (see hasUninstrumentedCanvases command)." }, { "name": "traceLogsRemoved", "parameters": [ { "name": "frameId", "$ref": "Page.FrameId", "optional": true, "description": "If given, trace logs from the given frame were removed." }, { "name": "traceLogId", "$ref": "TraceLogId", "optional": true, "description": "If given, trace log with the given ID was removed." } ], "description": "Fired when a set of trace logs were removed from the backend. If no parameters are given, all trace logs were removed." } ] }, { "domain": "Input", "types": [ { "id": "TouchPoint", "type": "object", "hidden": true, "properties": [ { "name": "state", "type": "string", "enum": ["touchPressed", "touchReleased", "touchMoved", "touchStationary", "touchCancelled"], "description": "State of the touch point." }, { "name": "x", "type": "integer", "description": "X coordinate of the event relative to the main frame's viewport."}, { "name": "y", "type": "integer", "description": "Y coordinate of the event relative to the main frame's viewport. 0 refers to the top of the viewport and Y increases as it proceeds towards the bottom of the viewport."}, { "name": "radiusX", "type": "integer", "optional": true, "description": "X radius of the touch area (default: 1)."}, { "name": "radiusY", "type": "integer", "optional": true, "description": "Y radius of the touch area (default: 1)."}, { "name": "rotationAngle", "type": "number", "optional": true, "description": "Rotation angle (default: 0.0)."}, { "name": "force", "type": "number", "optional": true, "description": "Force (default: 1.0)."}, { "name": "id", "type": "number", "optional": true, "description": "Identifier used to track touch sources between events, must be unique within an event."} ] } ], "commands": [ { "name": "dispatchKeyEvent", "parameters": [ { "name": "type", "type": "string", "enum": ["keyDown", "keyUp", "rawKeyDown", "char"], "description": "Type of the key event." }, { "name": "modifiers", "type": "integer", "optional": true, "description": "Bit field representing pressed modifier keys. Alt=1, Ctrl=2, Meta/Command=4, Shift=8 (default: 0)." }, { "name": "timestamp", "type": "number", "optional": true, "description": "Time at which the event occurred. Measured in UTC time in seconds since January 1, 1970 (default: current time)." }, { "name": "text", "type": "string", "optional": true, "description": "Text as generated by processing a virtual key code with a keyboard layout. Not needed for for keyUp and rawKeyDown events (default: \"\")" }, { "name": "unmodifiedText", "type": "string", "optional": true, "description": "Text that would have been generated by the keyboard if no modifiers were pressed (except for shift). Useful for shortcut (accelerator) key handling (default: \"\")." }, { "name": "keyIdentifier", "type": "string", "optional": true, "description": "Unique key identifier (e.g., 'U+0041') (default: \"\")." }, { "name": "windowsVirtualKeyCode", "type": "integer", "optional": true, "description": "Windows virtual key code (default: 0)." }, { "name": "nativeVirtualKeyCode", "type": "integer", "optional": true, "description": "Native virtual key code (default: 0)." }, { "name": "macCharCode", "type": "integer", "optional": true, "description": "Mac character code (default: 0)." }, { "name": "autoRepeat", "type": "boolean", "optional": true, "description": "Whether the event was generated from auto repeat (default: false)." }, { "name": "isKeypad", "type": "boolean", "optional": true, "description": "Whether the event was generated from the keypad (default: false)." }, { "name": "isSystemKey", "type": "boolean", "optional": true, "description": "Whether the event was a system key event (default: false)." } ], "description": "Dispatches a key event to the page." }, { "name": "dispatchMouseEvent", "parameters": [ { "name": "type", "type": "string", "enum": ["mousePressed", "mouseReleased", "mouseMoved"], "description": "Type of the mouse event." }, { "name": "x", "type": "integer", "description": "X coordinate of the event relative to the main frame's viewport."}, { "name": "y", "type": "integer", "description": "Y coordinate of the event relative to the main frame's viewport. 0 refers to the top of the viewport and Y increases as it proceeds towards the bottom of the viewport."}, { "name": "modifiers", "type": "integer", "optional": true, "description": "Bit field representing pressed modifier keys. Alt=1, Ctrl=2, Meta/Command=4, Shift=8 (default: 0)." }, { "name": "timestamp", "type": "number", "optional": true, "description": "Time at which the event occurred. Measured in UTC time in seconds since January 1, 1970 (default: current time)." }, { "name": "button", "type": "string", "enum": ["none", "left", "middle", "right"], "optional": true, "description": "Mouse button (default: \"none\")." }, { "name": "clickCount", "type": "integer", "optional": true, "description": "Number of times the mouse button was clicked (default: 0)." }, { "name": "deviceSpace", "type": "boolean", "optional": true, "hidden": true, "description": "If true, x and y are given in dip wrt current viewport." } ], "description": "Dispatches a mouse event to the page." }, { "name": "dispatchTouchEvent", "hidden": true, "parameters": [ { "name": "type", "type": "string", "enum": ["touchStart", "touchEnd", "touchMove"], "description": "Type of the touch event." }, { "name": "touchPoints", "type": "array", "items": { "$ref": "TouchPoint" }, "description": "Touch points." }, { "name": "modifiers", "type": "integer", "optional": true, "description": "Bit field representing pressed modifier keys. Alt=1, Ctrl=2, Meta/Command=4, Shift=8 (default: 0)." }, { "name": "timestamp", "type": "number", "optional": true, "description": "Time at which the event occurred. Measured in UTC time in seconds since January 1, 1970 (default: current time)." } ], "description": "Dispatches a touch event to the page." }, { "name": "dispatchGestureEvent", "hidden": true, "parameters": [ { "name": "type", "type": "string", "enum": ["scrollBegin", "scrollEnd", "scrollUpdate", "tapDown", "tap", "pinchBegin", "pinchEnd", "pinchUpdate"], "description": "Type of the gesture event." }, { "name": "x", "type": "integer", "description": "X coordinate relative to the screen's viewport."}, { "name": "y", "type": "integer", "description": "Y coordinate relative to the screen's viewport."}, { "name": "timestamp", "type": "number", "optional": true, "description": "Time at which the event occurred. Measured in UTC time in seconds since January 1, 1970 (default: current time)." }, { "name": "deltaX", "type": "integer", "optional": true, "description": "Delta X where apllies."}, { "name": "deltaY", "type": "integer", "optional": true, "description": "Delta Y where apllies."}, { "name": "pinchScale", "type": "number", "optional": true, "description": "Pinch scale." } ], "description": "Dispatches a gesture event to the page." } ], "events": [] }, { "domain": "LayerTree", "hidden": true, "types": [ { "id": "LayerId", "type": "string", "description": "Unique RenderLayer identifier." }, { "id": "Layer", "type": "object", "description": "Information about a compositing layer.", "properties": [ { "name": "layerId", "$ref": "LayerId", "description": "The unique id for this layer." }, { "name": "parentLayerId", "$ref": "LayerId", "optional": true, "description": "The id of parent (not present for root)." }, { "name": "nodeId", "$ref": "DOM.NodeId", "optional": true, "description": "The id for the node associated with this layer." }, { "name": "offsetX", "type": "number", "description": "Offset from parent layer, X coordinate." }, { "name": "offsetY", "type": "number", "description": "Offset from parent layer, X coordinate." }, { "name": "width", "type": "number", "description": "Layer width." }, { "name": "height", "type": "number", "description": "Layer height." }, { "name": "transform", "type": "array", "items": { "type": "number" }, "minItems": 16, "maxItems": 16, "optional": true, "description": "Transformation matrix for layer, default is identity matrix" }, { "name": "anchorX", "type": "number", "optional": true, "description": "Transform anchor point X, absent if no transform specified" }, { "name": "anchorY", "type": "number", "optional": true, "description": "Transform anchor point Y, absent if no transform specified" }, { "name": "anchorZ", "type": "number", "optional": true, "description": "Transform anchor point Z, absent if no transform specified" }, { "name": "paintCount", "type": "integer", "description": "Indicates how many time this layer has painted." }, { "name": "invisible", "type": "boolean", "optional": true, "description": "Set if layer is not visible." } ] } ], "commands": [ { "name": "enable", "description": "Enables compositing tree inspection." }, { "name": "disable", "description": "Disables compositing tree inspection." }, { "name": "getLayers", "parameters": [ { "name": "nodeId", "optional": true, "$ref": "DOM.NodeId", "description": "Root of the subtree for which we want to gather layers (return entire tree if not specified)" } ], "description": "Returns the layer tree structure of the current page.", "returns": [ { "name": "layers", "type": "array", "items": { "$ref": "Layer" }, "description": "Child layers." } ] }, { "name": "compositingReasons", "parameters": [ { "name": "layerId", "$ref": "LayerId", "description": "The id of the layer for which we want to get the reasons it was composited." } ], "description": "Provides the reasons why the given layer was composited.", "returns": [ { "name": "compositingReasons", "type": "array", "items": { "type": "string" }, "description": "A list of strings specifying reasons for the given layer to become composited." } ] } ], "events": [ { "name": "layerTreeDidChange" } ] }, { "domain": "Tracing", "hidden": true, "commands": [ { "name": "start", "description": "Strart trace events collection.", "parameters": [ { "name": "categories", "type": "string", "description": "Category/tag filter" } ] }, { "name": "end", "description": "Stop trace events collection." } ], "events": [ { "name": "dataCollected", "parameters": [ { "name": "value", "type": "array", "items": { "type": "object" } } ] }, { "name": "tracingComplete" } ] }] } ================================================ FILE: src/LiveDevelopment/Inspector/inspector.html ================================================ Inspector 1.1 Documentation

Table of Contents

ApplicationCache

Type Command Event

CSS

Type Command
  • CSS.enable: Enables the CSS agent for the given page. Clients should not assume that the CSS agent has been enabled until the result of this command is received.
  • CSS.disable: Disables the CSS agent for the given page.
  • CSS.getMatchedStylesForNode: Returns requested styles for a DOM node identified by nodeId.
  • CSS.getInlineStylesForNode: Returns the styles defined inline (explicitly in the "style" attribute and implicitly, using DOM attributes) for a DOM node identified by nodeId.
  • CSS.getComputedStyleForNode: Returns the computed style for a DOM node identified by nodeId.
  • CSS.getPlatformFontsForNode: Requests information about platform fonts which we used to render child TextNodes in the given node.
  • CSS.getAllStyleSheets: Returns metainfo entries for all known stylesheets.
  • CSS.getStyleSheet: Returns stylesheet data for the specified styleSheetId.
  • CSS.getStyleSheetText: Returns the current textual content and the URL for a stylesheet.
  • CSS.setStyleSheetText: Sets the new stylesheet text, thereby invalidating all existing CSSStyleId's and CSSRuleId's contained by this stylesheet.
  • CSS.setStyleText: Updates the CSSStyleDeclaration text.
  • CSS.setPropertyText: Sets the new text for a property in the respective style, at offset propertyIndex. If overwrite is true, a property at the given offset is overwritten, otherwise inserted. text entirely replaces the property name: value.
  • CSS.toggleProperty: Toggles the property in the respective style, at offset propertyIndex. The disable parameter denotes whether the property should be disabled (i.e. removed from the style declaration). If disable == false, the property gets put back into its original place in the style declaration.
  • CSS.setRuleSelector: Modifies the rule selector.
  • CSS.addRule: Creates a new empty rule with the given selector in a special "inspector" stylesheet in the owner document of the context node.
  • CSS.getSupportedCSSProperties: Returns all supported CSS property names.
  • CSS.forcePseudoState: Ensures that the given node will have specified pseudo-classes whenever its style is computed by the browser.
  • CSS.getNamedFlowCollection: Returns the Named Flows from the document.
Event

Canvas

Type Command Event
  • Canvas.contextCreated: Fired when a canvas context has been created in the given frame. The context may not be instrumented (see hasUninstrumentedCanvases command).
  • Canvas.traceLogsRemoved: Fired when a set of trace logs were removed from the backend. If no parameters are given, all trace logs were removed.

Console

Type Command Event

DOM

Type
  • DOM.NodeId: Unique DOM node identifier.
  • DOM.BackendNodeId: Unique DOM node identifier used to reference a node that may not have been pushed to the front-end.
  • DOM.PseudoType: Pseudo element type.
  • DOM.Node: DOM interaction is implemented in terms of mirror objects that represent the actual DOM nodes. DOMNode is a base node mirror type.
  • DOM.EventListener: DOM interaction is implemented in terms of mirror objects that represent the actual DOM nodes. DOMNode is a base node mirror type.
  • DOM.RGBA: A structure holding an RGBA color.
  • DOM.Quad: An array of quad vertices, x immediately followed by y for each point, points clock-wise.
  • DOM.BoxModel: Box model.
  • DOM.Rect: Rectangle.
  • DOM.HighlightConfig: Configuration data for the highlighting of page elements.
Command
  • DOM.enable: Enables DOM agent for the given page.
  • DOM.disable: Disables DOM agent for the given page.
  • DOM.getDocument: Returns the root DOM node to the caller.
  • DOM.requestChildNodes: Requests that children of the node with given id are returned to the caller in form of setChildNodes events where not only immediate children are retrieved, but all children down to the specified depth.
  • DOM.querySelector: Executes querySelector on a given node.
  • DOM.querySelectorAll: Executes querySelectorAll on a given node.
  • DOM.setNodeName: Sets node name for a node with given id.
  • DOM.setNodeValue: Sets node value for a node with given id.
  • DOM.removeNode: Removes node with given id.
  • DOM.setAttributeValue: Sets attribute for an element with given id.
  • DOM.setAttributesAsText: Sets attributes on element with given id. This method is useful when user edits some existing attribute value and types in several attribute name/value pairs.
  • DOM.removeAttribute: Removes attribute with given name from an element with given id.
  • DOM.getEventListenersForNode: Returns event listeners relevant to the node.
  • DOM.getOuterHTML: Returns node's HTML markup.
  • DOM.setOuterHTML: Sets node HTML markup, returns new node id.
  • DOM.performSearch: Searches for a given string in the DOM tree. Use getSearchResults to access search results or cancelSearch to end this search session.
  • DOM.getSearchResults: Returns search results from given fromIndex to given toIndex from the sarch with the given identifier.
  • DOM.discardSearchResults: Discards search results from the session with the given id. getSearchResults should no longer be called for that search.
  • DOM.requestNode: Requests that the node is sent to the caller given the JavaScript node object reference. All nodes that form the path from the node to the root are also sent to the client as a series of setChildNodes notifications.
  • DOM.setInspectModeEnabled: Enters the 'inspect' mode. In this mode, elements that user is hovering over are highlighted. Backend then generates 'inspectNodeRequested' event upon element selection.
  • DOM.highlightRect: Highlights given rectangle. Coordinates are absolute with respect to the main frame viewport.
  • DOM.highlightQuad: Highlights given quad. Coordinates are absolute with respect to the main frame viewport.
  • DOM.highlightNode: Highlights DOM node with given id or with the given JavaScript object wrapper. Either nodeId or objectId must be specified.
  • DOM.hideHighlight: Hides DOM node highlight.
  • DOM.highlightFrame: Highlights owner element of the frame with given id.
  • DOM.pushNodeByPathToFrontend: Requests that the node is sent to the caller given its path. // FIXME, use XPath
  • DOM.pushNodeByBackendIdToFrontend: Requests that the node is sent to the caller given its backend node id.
  • DOM.releaseBackendNodeIds: Requests that group of BackendNodeIds is released.
  • DOM.resolveNode: Resolves JavaScript node object for given node id.
  • DOM.getAttributes: Returns attributes for the specified node.
  • DOM.moveTo: Moves node into the new container, places it before the given anchor.
  • DOM.undo: Undoes the last performed action.
  • DOM.redo: Re-does the last undone action.
  • DOM.markUndoableState: Marks last undoable state.
  • DOM.focus: Focuses the given element.
  • DOM.setFileInputFiles: Sets files for the given file input element.
  • DOM.getBoxModel: Returns boxes for the currently selected nodes.
  • DOM.getNodeForLocation: Returns node id at given location.
Event

DOMDebugger

Type Command

DOMStorage

Type Command Event

Database

Type Command Event

Debugger

Type Command Event

FileSystem

Type Command

HeapProfiler

Type Command Event

IndexedDB

Type Command

Input

Type Command

Inspector

Command Event

LayerTree

Type Command Event

Memory

Type Command Event

Network

Type Command Event

Page

Type Command Event

Profiler

Type Command Event

Runtime

Type Command
  • Runtime.evaluate: Evaluates expression on global object.
  • Runtime.callFunctionOn: Calls function with given declaration on the given object. Object group of the result is inherited from the target object.
  • Runtime.getProperties: Returns properties of a given object. Object group of the result is inherited from the target object.
  • Runtime.releaseObject: Releases remote object with given id.
  • Runtime.releaseObjectGroup: Releases all remote objects that belong to a given group.
  • Runtime.run: Tells inspected instance(worker or page) that it can run in case it was started paused.
  • Runtime.enable: Enables reporting of execution contexts creation by means of executionContextCreated event. When the reporting gets enabled the event will be sent immediately for each existing execution context.
  • Runtime.disable: Disables reporting of execution contexts creation.
Event

Timeline

Type Command Event

Tracing

Command Event

Worker

Command Event

ApplicationCache

Type Command Event

ApplicationCache.ApplicationCacheResource Type

Detailed application cache resource information.

url
String Resource url.
size
Integer Resource size.
type
String Resource type.

ApplicationCache.ApplicationCache Type

Detailed application cache information.

manifestURL
String Manifest URL.
size
Number Application cache size.
creationTime
Number Application cache creation time.
updateTime
Number Application cache update time.
resources
[ApplicationCache.ApplicationCacheResource] Application cache resources.

ApplicationCache.FrameWithManifest Type

Frame identifier - manifest URL pair.

frameId
Page.FrameId Frame identifier.
manifestURL
String Manifest URL.
status
Integer Application cache status.

ApplicationCache.getFramesWithManifests Command

Returns array of frame identifiers with manifest urls for each frame containing a document associated with some application cache.

Callback Parameters:

frameIds
[ApplicationCache.FrameWithManifest] Array of frame identifiers with manifest urls for each frame containing a document associated with some application cache.

Code Example:

// WebInspector Command: ApplicationCache.getFramesWithManifests
ApplicationCache.getFramesWithManifests(function callback(res) {
	// res = {frameIds}
});

ApplicationCache.enable Command

Enables application cache domain notifications.

Code Example:

// WebInspector Command: ApplicationCache.enable
ApplicationCache.enable();

ApplicationCache.getManifestForFrame Command

Returns manifest URL for document in the given frame.

frameId
Page.FrameId Identifier of the frame containing document whose manifest is retrieved.

Callback Parameters:

manifestURL
String Manifest URL for document in the given frame.

Code Example:

// WebInspector Command: ApplicationCache.getManifestForFrame
ApplicationCache.getManifestForFrame(frameId, function callback(res) {
	// res = {manifestURL}
});

ApplicationCache.getApplicationCacheForFrame Command

Returns relevant application cache data for the document in given frame.

frameId
Page.FrameId Identifier of the frame containing document whose application cache is retrieved.

Callback Parameters:

applicationCache
ApplicationCache.ApplicationCache Relevant application cache data for the document in given frame.

Code Example:

// WebInspector Command: ApplicationCache.getApplicationCacheForFrame
ApplicationCache.getApplicationCacheForFrame(frameId, function callback(res) {
	// res = {applicationCache}
});

ApplicationCache.applicationCacheStatusUpdated Event

frameId
Page.FrameId Identifier of the frame containing document whose application cache updated status.
manifestURL
String Manifest URL.
status
Integer Updated application cache status.

Code Example:

// WebInspector Event: ApplicationCache.applicationCacheStatusUpdated
function onApplicationCacheStatusUpdated(res) {
	// res = {frameId, manifestURL, status}
}

ApplicationCache.networkStateUpdated Event

isNowOnline
Boolean

Code Example:

// WebInspector Event: ApplicationCache.networkStateUpdated
function onNetworkStateUpdated(res) {
	// res = {isNowOnline}
}

CSS

This domain exposes CSS read/write operations. All CSS objects (stylesheets, rules, and styles) have an associated id used in subsequent operations on the related object. Each object type has a specific id structure, and those are not interchangeable between objects of different kinds. CSS objects can be loaded using the get*ForNode() calls (which accept a DOM node id). A client can also discover all the existing stylesheets with the getAllStyleSheets() method (or keeping track of the styleSheetAdded/styleSheetRemoved events) and subsequently load the required stylesheet contents using the getStyleSheet[Text]() methods.

Type Command
  • CSS.enable: Enables the CSS agent for the given page. Clients should not assume that the CSS agent has been enabled until the result of this command is received.
  • CSS.disable: Disables the CSS agent for the given page.
  • CSS.getMatchedStylesForNode: Returns requested styles for a DOM node identified by nodeId.
  • CSS.getInlineStylesForNode: Returns the styles defined inline (explicitly in the "style" attribute and implicitly, using DOM attributes) for a DOM node identified by nodeId.
  • CSS.getComputedStyleForNode: Returns the computed style for a DOM node identified by nodeId.
  • CSS.getPlatformFontsForNode: Requests information about platform fonts which we used to render child TextNodes in the given node.
  • CSS.getAllStyleSheets: Returns metainfo entries for all known stylesheets.
  • CSS.getStyleSheet: Returns stylesheet data for the specified styleSheetId.
  • CSS.getStyleSheetText: Returns the current textual content and the URL for a stylesheet.
  • CSS.setStyleSheetText: Sets the new stylesheet text, thereby invalidating all existing CSSStyleId's and CSSRuleId's contained by this stylesheet.
  • CSS.setStyleText: Updates the CSSStyleDeclaration text.
  • CSS.setPropertyText: Sets the new text for a property in the respective style, at offset propertyIndex. If overwrite is true, a property at the given offset is overwritten, otherwise inserted. text entirely replaces the property name: value.
  • CSS.toggleProperty: Toggles the property in the respective style, at offset propertyIndex. The disable parameter denotes whether the property should be disabled (i.e. removed from the style declaration). If disable == false, the property gets put back into its original place in the style declaration.
  • CSS.setRuleSelector: Modifies the rule selector.
  • CSS.addRule: Creates a new empty rule with the given selector in a special "inspector" stylesheet in the owner document of the context node.
  • CSS.getSupportedCSSProperties: Returns all supported CSS property names.
  • CSS.forcePseudoState: Ensures that the given node will have specified pseudo-classes whenever its style is computed by the browser.
  • CSS.getNamedFlowCollection: Returns the Named Flows from the document.
Event

CSS.StyleSheetId Type

String

CSS.CSSStyleId Type

This object identifies a CSS style in a unique way.

styleSheetId
CSS.StyleSheetId Enclosing stylesheet identifier.
ordinal
Integer The style ordinal within the stylesheet.

CSS.StyleSheetOrigin Type

Stylesheet type: "user" for user stylesheets, "user-agent" for user-agent stylesheets, "inspector" for stylesheets created by the inspector (i.e. those holding the "via inspector" rules), "regular" for regular stylesheets.

( user | user-agent | inspector | regular )

CSS.CSSRuleId Type

This object identifies a CSS rule in a unique way.

styleSheetId
CSS.StyleSheetId Enclosing stylesheet identifier.
ordinal
Integer The rule ordinal within the stylesheet.

CSS.PseudoIdMatches Type

CSS rule collection for a single pseudo style.

pseudoId
Integer Pseudo style identifier (see enum PseudoId in RenderStyleConstants.h).
matches
[CSS.RuleMatch] Matches of CSS rules applicable to the pseudo style.

CSS.InheritedStyleEntry Type

CSS rule collection for a single pseudo style.

inlineStyle (optional)
CSS.CSSStyle The ancestor node's inline style, if any, in the style inheritance chain.
matchedCSSRules
[CSS.RuleMatch] Matches of CSS rules matching the ancestor node in the style inheritance chain.

CSS.RuleMatch Type

Match data for a CSS rule.

rule
CSS.CSSRule CSS rule in the match.
matchingSelectors
[Integer] Matching selector indices in the rule's selectorList selectors (0-based).

CSS.SelectorList Type

Selector list data.

selectors
[String] Selectors in the list.
text
String Rule selector text.
range (optional)
CSS.SourceRange Rule selector range in the underlying resource (if available).

CSS.CSSStyleAttribute Type

CSS style information for a DOM style attribute.

name
String DOM attribute name (e.g. "width").
style
CSS.CSSStyle CSS style generated by the respective DOM attribute.

CSS.CSSStyleSheetHeader Type

CSS stylesheet metainformation.

styleSheetId
CSS.StyleSheetId The stylesheet identifier.
frameId
Page.FrameId Owner frame identifier.
sourceURL
String Stylesheet resource URL.
sourceMapURL (optional)
String URL of source map associated with the stylesheet (if any).
origin
CSS.StyleSheetOrigin Stylesheet origin.
title
String Stylesheet title.
disabled
Boolean Denotes whether the stylesheet is disabled.
hasSourceURL (optional)
Boolean Whether the sourceURL field value comes from the sourceURL comment.
isInline
Boolean Whether this stylesheet is created for STYLE tag by parser. This flag is not set for document.written STYLE tags.
startLine
Number Line offset of the stylesheet within the resource (zero based).
startColumn
Number Column offset of the stylesheet within the resource (zero based).

CSS.CSSStyleSheetBody Type

CSS stylesheet contents.

styleSheetId
CSS.StyleSheetId The stylesheet identifier.
rules
[CSS.CSSRule] Stylesheet resource URL.
text (optional)
String Stylesheet resource contents (if available).

CSS.CSSRule Type

CSS rule representation.

ruleId (optional)
CSS.CSSRuleId The CSS rule identifier (absent for user agent stylesheet and user-specified stylesheet rules).
selectorList
CSS.SelectorList Rule selector data.
sourceURL (optional)
String Parent stylesheet resource URL (for regular rules).
origin
CSS.StyleSheetOrigin Parent stylesheet's origin.
style
CSS.CSSStyle Associated style declaration.
media (optional)
[CSS.CSSMedia] Media list array (for rules involving media queries). The array enumerates media queries starting with the innermost one, going outwards.

CSS.SourceRange Type

Text range within a resource. All numbers are zero-based.

startLine
Integer Start line of range.
startColumn
Integer Start column of range (inclusive).
endLine
Integer End line of range
endColumn
Integer End column of range (exclusive).

CSS.ShorthandEntry Type

name
String Shorthand name.
value
String Shorthand value.

CSS.CSSPropertyInfo Type

name
String Property name.
longhands (optional)
[String] Longhand property names.

CSS.CSSComputedStyleProperty Type

name
String Computed style property name.
value
String Computed style property value.

CSS.CSSStyle Type

CSS style representation.

styleId (optional)
CSS.CSSStyleId The CSS style identifier (absent for attribute styles).
cssProperties
[CSS.CSSProperty] CSS properties in the style.
shorthandEntries
[CSS.ShorthandEntry] Computed values for all shorthands found in the style.
cssText (optional)
String Style declaration text (if available).
range (optional)
CSS.SourceRange Style declaration range in the enclosing stylesheet (if available).
width (optional)
String The effective "width" property value from this style.
height (optional)
String The effective "height" property value from this style.

CSS.CSSProperty Type

CSS property declaration data.

name
String The property name.
value
String The property value.
priority (optional)
String The property priority (implies "" if absent).
implicit (optional)
Boolean Whether the property is implicit (implies false if absent).
text (optional)
String The full property text as specified in the style.
parsedOk (optional)
Boolean Whether the property is understood by the browser (implies true if absent).
status (optional)
( active | inactive | disabled | style ) The property status: "active" if the property is effective in the style, "inactive" if the property is overridden by a same-named property in this style later on, "disabled" if the property is disabled by the user, "style" (implied if absent) if the property is reported by the browser rather than by the CSS source parser.
range (optional)
CSS.SourceRange The entire property range in the enclosing style declaration (if available).

CSS.CSSMedia Type

CSS media query descriptor.

text
String Media query text.
source
( mediaRule | importRule | linkedSheet | inlineSheet ) Source of the media query: "mediaRule" if specified by a @media rule, "importRule" if specified by an @import rule, "linkedSheet" if specified by a "media" attribute in a linked stylesheet's LINK tag, "inlineSheet" if specified by a "media" attribute in an inline stylesheet's STYLE tag.
sourceURL (optional)
String URL of the document containing the media query description.
range (optional)
CSS.SourceRange The associated rule (@media or @import) header range in the enclosing stylesheet (if available).
parentStyleSheetId (optional)
CSS.StyleSheetId Identifier of the stylesheet containing this object (if exists).

CSS.SelectorProfileEntry Type

CSS selector profile entry.

selector
String CSS selector of the corresponding rule.
url
String URL of the resource containing the corresponding rule.
lineNumber
Integer Selector line number in the resource for the corresponding rule.
time
Number Total time this rule handling contributed to the browser running time during profiling (in milliseconds).
hitCount
Integer Number of times this rule was considered a candidate for matching against DOM elements.
matchCount
Integer Number of times this rule actually matched a DOM element.

CSS.SelectorProfile Type

totalTime
Number Total processing time for all selectors in the profile (in milliseconds).
data
[CSS.SelectorProfileEntry] CSS selector profile entries.

CSS.Region Type

This object represents a region that flows from a Named Flow.

regionOverset
( overset | fit | empty ) The "overset" attribute of a Named Flow.
nodeId
DOM.NodeId The corresponding DOM node id.

CSS.NamedFlow Type

This object represents a Named Flow.

documentNodeId
DOM.NodeId The document node id.
name
String Named Flow identifier.
overset
Boolean The "overset" attribute of a Named Flow.
content
[DOM.NodeId] An array of nodes that flow into the Named Flow.
regions
[CSS.Region] An array of regions associated with the Named Flow.

CSS.PlatformFontUsage Type

Information about amount of glyphs that were rendered with given font.

familyName
String Font's family name reported by platform.
glyphCount
Number Amount of glyphs that were rendered with this font.

CSS.enable Command

Enables the CSS agent for the given page. Clients should not assume that the CSS agent has been enabled until the result of this command is received.

Code Example:

// WebInspector Command: CSS.enable
CSS.enable();

CSS.disable Command

Disables the CSS agent for the given page.

Code Example:

// WebInspector Command: CSS.disable
CSS.disable();

CSS.getMatchedStylesForNode Command

Returns requested styles for a DOM node identified by nodeId.

nodeId
DOM.NodeId
includePseudo (optional)
Boolean Whether to include pseudo styles (default: true).
includeInherited (optional)
Boolean Whether to include inherited styles (default: true).

Callback Parameters:

matchedCSSRules (optional)
[CSS.RuleMatch] CSS rules matching this node, from all applicable stylesheets.
pseudoElements (optional)
[CSS.PseudoIdMatches] Pseudo style matches for this node.
inherited (optional)
[CSS.InheritedStyleEntry] A chain of inherited styles (from the immediate node parent up to the DOM tree root).

Code Example:

// WebInspector Command: CSS.getMatchedStylesForNode
CSS.getMatchedStylesForNode(nodeId, includePseudo, includeInherited, function callback(res) {
	// res = {matchedCSSRules, pseudoElements, inherited}
});

CSS.getInlineStylesForNode Command

Returns the styles defined inline (explicitly in the "style" attribute and implicitly, using DOM attributes) for a DOM node identified by nodeId.

nodeId
DOM.NodeId

Callback Parameters:

inlineStyle (optional)
CSS.CSSStyle Inline style for the specified DOM node.
attributesStyle (optional)
CSS.CSSStyle Attribute-defined element style (e.g. resulting from "width=20 height=100%").

Code Example:

// WebInspector Command: CSS.getInlineStylesForNode
CSS.getInlineStylesForNode(nodeId, function callback(res) {
	// res = {inlineStyle, attributesStyle}
});

CSS.getComputedStyleForNode Command

Returns the computed style for a DOM node identified by nodeId.

nodeId
DOM.NodeId

Callback Parameters:

computedStyle
[CSS.CSSComputedStyleProperty] Computed style for the specified DOM node.

Code Example:

// WebInspector Command: CSS.getComputedStyleForNode
CSS.getComputedStyleForNode(nodeId, function callback(res) {
	// res = {computedStyle}
});

CSS.getPlatformFontsForNode Command

Requests information about platform fonts which we used to render child TextNodes in the given node.

nodeId
DOM.NodeId

Callback Parameters:

cssFamilyName
String Font family name which is determined by computed style.
fonts
[CSS.PlatformFontUsage] Usage statistics for every employed platform font.

Code Example:

// WebInspector Command: CSS.getPlatformFontsForNode
CSS.getPlatformFontsForNode(nodeId, function callback(res) {
	// res = {cssFamilyName, fonts}
});

CSS.getAllStyleSheets Command

Returns metainfo entries for all known stylesheets.

Callback Parameters:

headers
[CSS.CSSStyleSheetHeader] Descriptor entries for all available stylesheets.

Code Example:

// WebInspector Command: CSS.getAllStyleSheets
CSS.getAllStyleSheets(function callback(res) {
	// res = {headers}
});

CSS.getStyleSheet Command

Returns stylesheet data for the specified styleSheetId.

styleSheetId
CSS.StyleSheetId

Callback Parameters:

styleSheet
CSS.CSSStyleSheetBody Stylesheet contents for the specified styleSheetId.

Code Example:

// WebInspector Command: CSS.getStyleSheet
CSS.getStyleSheet(styleSheetId, function callback(res) {
	// res = {styleSheet}
});

CSS.getStyleSheetText Command

Returns the current textual content and the URL for a stylesheet.

styleSheetId
CSS.StyleSheetId

Callback Parameters:

text
String The stylesheet text.

Code Example:

// WebInspector Command: CSS.getStyleSheetText
CSS.getStyleSheetText(styleSheetId, function callback(res) {
	// res = {text}
});

CSS.setStyleSheetText Command

Sets the new stylesheet text, thereby invalidating all existing CSSStyleId's and CSSRuleId's contained by this stylesheet.

styleSheetId
CSS.StyleSheetId
text
String

Code Example:

// WebInspector Command: CSS.setStyleSheetText
CSS.setStyleSheetText(styleSheetId, text);

CSS.setStyleText Command

Updates the CSSStyleDeclaration text.

styleId
CSS.CSSStyleId
text
String

Callback Parameters:

style
CSS.CSSStyle The resulting style after the text modification.

Code Example:

// WebInspector Command: CSS.setStyleText
CSS.setStyleText(styleId, text, function callback(res) {
	// res = {style}
});

CSS.setPropertyText Command

Sets the new text for a property in the respective style, at offset propertyIndex. If overwrite is true, a property at the given offset is overwritten, otherwise inserted. text entirely replaces the property name: value.

styleId
CSS.CSSStyleId
propertyIndex
Integer
text
String
overwrite
Boolean

Callback Parameters:

style
CSS.CSSStyle The resulting style after the property text modification.

Code Example:

// WebInspector Command: CSS.setPropertyText
CSS.setPropertyText(styleId, propertyIndex, text, overwrite, function callback(res) {
	// res = {style}
});

CSS.toggleProperty Command

Toggles the property in the respective style, at offset propertyIndex. The disable parameter denotes whether the property should be disabled (i.e. removed from the style declaration). If disable == false, the property gets put back into its original place in the style declaration.

styleId
CSS.CSSStyleId
propertyIndex
Integer
disable
Boolean

Callback Parameters:

style
CSS.CSSStyle The resulting style after the property toggling.

Code Example:

// WebInspector Command: CSS.toggleProperty
CSS.toggleProperty(styleId, propertyIndex, disable, function callback(res) {
	// res = {style}
});

CSS.setRuleSelector Command

Modifies the rule selector.

ruleId
CSS.CSSRuleId
selector
String

Callback Parameters:

rule
CSS.CSSRule The resulting rule after the selector modification.

Code Example:

// WebInspector Command: CSS.setRuleSelector
CSS.setRuleSelector(ruleId, selector, function callback(res) {
	// res = {rule}
});

CSS.addRule Command

Creates a new empty rule with the given selector in a special "inspector" stylesheet in the owner document of the context node.

contextNodeId
DOM.NodeId
selector
String

Callback Parameters:

rule
CSS.CSSRule The newly created rule.

Code Example:

// WebInspector Command: CSS.addRule
CSS.addRule(contextNodeId, selector, function callback(res) {
	// res = {rule}
});

CSS.getSupportedCSSProperties Command

Returns all supported CSS property names.

Callback Parameters:

cssProperties
[CSS.CSSPropertyInfo] Supported property metainfo.

Code Example:

// WebInspector Command: CSS.getSupportedCSSProperties
CSS.getSupportedCSSProperties(function callback(res) {
	// res = {cssProperties}
});

CSS.forcePseudoState Command

Ensures that the given node will have specified pseudo-classes whenever its style is computed by the browser.

nodeId
DOM.NodeId The element id for which to force the pseudo state.
forcedPseudoClasses
[( active | focus | hover | visited )] Element pseudo classes to force when computing the element's style.

Code Example:

// WebInspector Command: CSS.forcePseudoState
CSS.forcePseudoState(nodeId, forcedPseudoClasses);

CSS.getNamedFlowCollection Command

Returns the Named Flows from the document.

documentNodeId
DOM.NodeId The document node id for which to get the Named Flow Collection.

Callback Parameters:

namedFlows
[CSS.NamedFlow] An array containing the Named Flows in the document.

Code Example:

// WebInspector Command: CSS.getNamedFlowCollection
CSS.getNamedFlowCollection(documentNodeId, function callback(res) {
	// res = {namedFlows}
});

CSS.mediaQueryResultChanged Event

Fires whenever a MediaQuery result changes (for example, after a browser window has been resized.) The current implementation considers only viewport-dependent media features.

Code Example:

// WebInspector Event: CSS.mediaQueryResultChanged
function onMediaQueryResultChanged(res) {
	// res = {}
}

CSS.styleSheetChanged Event

Fired whenever a stylesheet is changed as a result of the client operation.

styleSheetId
CSS.StyleSheetId

Code Example:

// WebInspector Event: CSS.styleSheetChanged
function onStyleSheetChanged(res) {
	// res = {styleSheetId}
}

CSS.styleSheetAdded Event

Fired whenever an active document stylesheet is added.

header
CSS.CSSStyleSheetHeader Added stylesheet metainfo.

Code Example:

// WebInspector Event: CSS.styleSheetAdded
function onStyleSheetAdded(res) {
	// res = {header}
}

CSS.styleSheetRemoved Event

Fired whenever an active document stylesheet is removed.

styleSheetId
CSS.StyleSheetId Identifier of the removed stylesheet.

Code Example:

// WebInspector Event: CSS.styleSheetRemoved
function onStyleSheetRemoved(res) {
	// res = {styleSheetId}
}

CSS.namedFlowCreated Event

Fires when a Named Flow is created.

namedFlow
CSS.NamedFlow The new Named Flow.

Code Example:

// WebInspector Event: CSS.namedFlowCreated
function onNamedFlowCreated(res) {
	// res = {namedFlow}
}

CSS.namedFlowRemoved Event

Fires when a Named Flow is removed: has no associated content nodes and regions.

documentNodeId
DOM.NodeId The document node id.
flowName
String Identifier of the removed Named Flow.

Code Example:

// WebInspector Event: CSS.namedFlowRemoved
function onNamedFlowRemoved(res) {
	// res = {documentNodeId, flowName}
}

CSS.regionLayoutUpdated Event

Fires when a Named Flow's layout may have changed.

namedFlow
CSS.NamedFlow The Named Flow whose layout may have changed.

Code Example:

// WebInspector Event: CSS.regionLayoutUpdated
function onRegionLayoutUpdated(res) {
	// res = {namedFlow}
}

CSS.regionOversetChanged Event

Fires if any of the regionOverset values changed in a Named Flow's region chain.

namedFlow
CSS.NamedFlow The Named Flow containing the regions whose regionOverset values changed.

Code Example:

// WebInspector Event: CSS.regionOversetChanged
function onRegionOversetChanged(res) {
	// res = {namedFlow}
}

Canvas

Type Command Event
  • Canvas.contextCreated: Fired when a canvas context has been created in the given frame. The context may not be instrumented (see hasUninstrumentedCanvases command).
  • Canvas.traceLogsRemoved: Fired when a set of trace logs were removed from the backend. If no parameters are given, all trace logs were removed.

Canvas.ResourceId Type

Unique resource identifier.

String

Canvas.ResourceStateDescriptor Type

Resource state descriptor.

name
String State name.
enumValueForName (optional)
String String representation of the enum value, if name stands for an enum.
value (optional)
Canvas.CallArgument The value associated with the particular state.
values (optional)
[Canvas.ResourceStateDescriptor] Array of values associated with the particular state. Either value or values will be specified.
isArray (optional)
Boolean True iff the given values items stand for an array rather than a list of grouped states.

Canvas.ResourceState Type

Resource state.

id
Canvas.ResourceId
traceLogId
Canvas.TraceLogId
descriptors (optional)
[Canvas.ResourceStateDescriptor] Describes current Resource state.
imageURL (optional)
String Screenshot image data URL.

Canvas.CallArgument Type

description
String String representation of the object.
enumName (optional)
String Enum name, if any, that stands for the value (for example, a WebGL enum name).
resourceId (optional)
Canvas.ResourceId Resource identifier. Specified for Resource objects only.
type (optional)
( object | function | undefined | string | number | boolean ) Object type. Specified for non Resource objects only.
subtype (optional)
( array | null | node | regexp | date ) Object subtype hint. Specified for object type values only.
remoteObject (optional)
Runtime.RemoteObject The RemoteObject, if requested.

Canvas.Call Type

contextId
Canvas.ResourceId
functionName (optional)
String
arguments (optional)
[Canvas.CallArgument]
result (optional)
Canvas.CallArgument
isDrawingCall (optional)
Boolean
isFrameEndCall (optional)
Boolean
property (optional)
String
value (optional)
Canvas.CallArgument
sourceURL (optional)
String
lineNumber (optional)
Integer
columnNumber (optional)
Integer

Canvas.TraceLogId Type

Unique trace log identifier.

String

Canvas.TraceLog Type

id
Canvas.TraceLogId
calls
[Canvas.Call]
contexts
[Canvas.CallArgument]
startOffset
Integer
alive
Boolean
totalAvailableCalls
Number

Canvas.enable Command

Enables Canvas inspection.

Code Example:

// WebInspector Command: Canvas.enable
Canvas.enable();

Canvas.disable Command

Disables Canvas inspection.

Code Example:

// WebInspector Command: Canvas.disable
Canvas.disable();

Canvas.dropTraceLog Command

traceLogId
Canvas.TraceLogId

Code Example:

// WebInspector Command: Canvas.dropTraceLog
Canvas.dropTraceLog(traceLogId);

Canvas.hasUninstrumentedCanvases Command

Checks if there is any uninstrumented canvas in the inspected page.

Callback Parameters:

result
Boolean

Code Example:

// WebInspector Command: Canvas.hasUninstrumentedCanvases
Canvas.hasUninstrumentedCanvases(function callback(res) {
	// res = {result}
});

Canvas.captureFrame Command

Starts (or continues) a canvas frame capturing which will be stopped automatically after the next frame is prepared.

frameId (optional)
Page.FrameId Identifier of the frame containing document whose canvases are to be captured. If omitted, main frame is assumed.

Callback Parameters:

traceLogId
Canvas.TraceLogId Identifier of the trace log containing captured canvas calls.

Code Example:

// WebInspector Command: Canvas.captureFrame
Canvas.captureFrame(frameId, function callback(res) {
	// res = {traceLogId}
});

Canvas.startCapturing Command

Starts (or continues) consecutive canvas frames capturing. The capturing is stopped by the corresponding stopCapturing command.

frameId (optional)
Page.FrameId Identifier of the frame containing document whose canvases are to be captured. If omitted, main frame is assumed.

Callback Parameters:

traceLogId
Canvas.TraceLogId Identifier of the trace log containing captured canvas calls.

Code Example:

// WebInspector Command: Canvas.startCapturing
Canvas.startCapturing(frameId, function callback(res) {
	// res = {traceLogId}
});

Canvas.stopCapturing Command

traceLogId
Canvas.TraceLogId

Code Example:

// WebInspector Command: Canvas.stopCapturing
Canvas.stopCapturing(traceLogId);

Canvas.getTraceLog Command

traceLogId
Canvas.TraceLogId
startOffset (optional)
Integer
maxLength (optional)
Integer

Callback Parameters:

traceLog
Canvas.TraceLog

Code Example:

// WebInspector Command: Canvas.getTraceLog
Canvas.getTraceLog(traceLogId, startOffset, maxLength, function callback(res) {
	// res = {traceLog}
});

Canvas.replayTraceLog Command

traceLogId
Canvas.TraceLogId
stepNo
Integer Last call index in the trace log to replay (zero based).

Callback Parameters:

resourceState
Canvas.ResourceState
replayTime
Number Replay time (in milliseconds).

Code Example:

// WebInspector Command: Canvas.replayTraceLog
Canvas.replayTraceLog(traceLogId, stepNo, function callback(res) {
	// res = {resourceState, replayTime}
});

Canvas.getResourceState Command

traceLogId
Canvas.TraceLogId
resourceId
Canvas.ResourceId

Callback Parameters:

resourceState
Canvas.ResourceState

Code Example:

// WebInspector Command: Canvas.getResourceState
Canvas.getResourceState(traceLogId, resourceId, function callback(res) {
	// res = {resourceState}
});

Canvas.evaluateTraceLogCallArgument Command

Evaluates a given trace call argument or its result.

traceLogId
Canvas.TraceLogId
callIndex
Integer Index of the call to evaluate on (zero based).
argumentIndex
Integer Index of the argument to evaluate (zero based). Provide -1 to evaluate call result.
objectGroup (optional)
String String object group name to put result into (allows rapid releasing resulting object handles using Runtime.releaseObjectGroup).

Callback Parameters:

result (optional)
Runtime.RemoteObject Object wrapper for the evaluation result.
resourceState (optional)
Canvas.ResourceState State of the Resource object.

Code Example:

// WebInspector Command: Canvas.evaluateTraceLogCallArgument
Canvas.evaluateTraceLogCallArgument(traceLogId, callIndex, argumentIndex, objectGroup, function callback(res) {
	// res = {result, resourceState}
});

Canvas.contextCreated Event

Fired when a canvas context has been created in the given frame. The context may not be instrumented (see hasUninstrumentedCanvases command).

frameId
Page.FrameId Identifier of the frame containing a canvas with a context.

Code Example:

// WebInspector Event: Canvas.contextCreated
function onContextCreated(res) {
	// res = {frameId}
}

Canvas.traceLogsRemoved Event

Fired when a set of trace logs were removed from the backend. If no parameters are given, all trace logs were removed.

frameId (optional)
Page.FrameId If given, trace logs from the given frame were removed.
traceLogId (optional)
Canvas.TraceLogId If given, trace log with the given ID was removed.

Code Example:

// WebInspector Event: Canvas.traceLogsRemoved
function onTraceLogsRemoved(res) {
	// res = {frameId, traceLogId}
}

Console

Console domain defines methods and events for interaction with the JavaScript console. Console collects messages created by means of the JavaScript Console API. One needs to enable this domain using enable command in order to start receiving the console messages. Browser collects messages issued while console domain is not enabled as well and reports them using messageAdded notification upon enabling.

Type Command Event

Console.Timestamp Type

Number of seconds since epoch.

Number

Console.ConsoleMessage Type

Console message.

source
( xml | javascript | network | console-api | storage | appcache | rendering | css | security | other | deprecation ) Message source.
level
( log | warning | error | debug ) Message severity.
text
String Message text.
type (optional)
( log | dir | dirxml | table | trace | clear | startGroup | startGroupCollapsed | endGroup | assert | profile | profileEnd ) Console message type.
url (optional)
String URL of the message origin.
line (optional)
Integer Line number in the resource that generated this message.
column (optional)
Integer Column number in the resource that generated this message.
repeatCount (optional)
Integer Repeat count for repeated messages.
parameters (optional)
[Runtime.RemoteObject] Message parameters in case of the formatted message.
stackTrace (optional)
Console.StackTrace JavaScript stack trace for assertions and error messages.
networkRequestId (optional)
Network.RequestId Identifier of the network request associated with this message.
timestamp
Console.Timestamp Timestamp, when this message was fired.

Console.CallFrame Type

Stack entry for console errors and assertions.

functionName
String JavaScript function name.
scriptId
String JavaScript script id.
url
String JavaScript script name or url.
lineNumber
Integer JavaScript script line number.
columnNumber
Integer JavaScript script column number.

Console.StackTrace Type

Call frames for assertions or error messages.

[Console.CallFrame]

Console.enable Command

Enables console domain, sends the messages collected so far to the client by means of the messageAdded notification.

Code Example:

// WebInspector Command: Console.enable
Console.enable();

Console.disable Command

Disables console domain, prevents further console messages from being reported to the client.

Code Example:

// WebInspector Command: Console.disable
Console.disable();

Console.clearMessages Command

Clears console messages collected in the browser.

Code Example:

// WebInspector Command: Console.clearMessages
Console.clearMessages();

Console.setMonitoringXHREnabled Command

Toggles monitoring of XMLHttpRequest. If true, console will receive messages upon each XHR issued.

enabled
Boolean Monitoring enabled state.

Code Example:

// WebInspector Command: Console.setMonitoringXHREnabled
Console.setMonitoringXHREnabled(enabled);

Console.addInspectedNode Command

Enables console to refer to the node with given id via $x (see Command Line API for more details $x functions).

nodeId
DOM.NodeId DOM node id to be accessible by means of $x command line API.

Code Example:

// WebInspector Command: Console.addInspectedNode
Console.addInspectedNode(nodeId);

Console.addInspectedHeapObject Command

heapObjectId
Integer

Code Example:

// WebInspector Command: Console.addInspectedHeapObject
Console.addInspectedHeapObject(heapObjectId);

Console.messageAdded Event

Issued when new console message is added.

message
Console.ConsoleMessage Console message that has been added.

Code Example:

// WebInspector Event: Console.messageAdded
function onMessageAdded(res) {
	// res = {message}
}

Console.messageRepeatCountUpdated Event

Issued when subsequent message(s) are equal to the previous one(s).

count
Integer New repeat count value.
timestamp
Console.Timestamp Timestamp of most recent message in batch.

Code Example:

// WebInspector Event: Console.messageRepeatCountUpdated
function onMessageRepeatCountUpdated(res) {
	// res = {count, timestamp}
}

Console.messagesCleared Event

Issued when console is cleared. This happens either upon clearMessages command or after page navigation.

Code Example:

// WebInspector Event: Console.messagesCleared
function onMessagesCleared(res) {
	// res = {}
}

DOM

This domain exposes DOM read/write operations. Each DOM Node is represented with its mirror object that has an id. This id can be used to get additional information on the Node, resolve it into the JavaScript object wrapper, etc. It is important that client receives DOM events only for the nodes that are known to the client. Backend keeps track of the nodes that were sent to the client and never sends the same node twice. It is client's responsibility to collect information about the nodes that were sent to the client.

Note that iframe owner elements will return corresponding document elements as their child nodes.

Type
  • DOM.NodeId: Unique DOM node identifier.
  • DOM.BackendNodeId: Unique DOM node identifier used to reference a node that may not have been pushed to the front-end.
  • DOM.PseudoType: Pseudo element type.
  • DOM.Node: DOM interaction is implemented in terms of mirror objects that represent the actual DOM nodes. DOMNode is a base node mirror type.
  • DOM.EventListener: DOM interaction is implemented in terms of mirror objects that represent the actual DOM nodes. DOMNode is a base node mirror type.
  • DOM.RGBA: A structure holding an RGBA color.
  • DOM.Quad: An array of quad vertices, x immediately followed by y for each point, points clock-wise.
  • DOM.BoxModel: Box model.
  • DOM.Rect: Rectangle.
  • DOM.HighlightConfig: Configuration data for the highlighting of page elements.
Command
  • DOM.enable: Enables DOM agent for the given page.
  • DOM.disable: Disables DOM agent for the given page.
  • DOM.getDocument: Returns the root DOM node to the caller.
  • DOM.requestChildNodes: Requests that children of the node with given id are returned to the caller in form of setChildNodes events where not only immediate children are retrieved, but all children down to the specified depth.
  • DOM.querySelector: Executes querySelector on a given node.
  • DOM.querySelectorAll: Executes querySelectorAll on a given node.
  • DOM.setNodeName: Sets node name for a node with given id.
  • DOM.setNodeValue: Sets node value for a node with given id.
  • DOM.removeNode: Removes node with given id.
  • DOM.setAttributeValue: Sets attribute for an element with given id.
  • DOM.setAttributesAsText: Sets attributes on element with given id. This method is useful when user edits some existing attribute value and types in several attribute name/value pairs.
  • DOM.removeAttribute: Removes attribute with given name from an element with given id.
  • DOM.getEventListenersForNode: Returns event listeners relevant to the node.
  • DOM.getOuterHTML: Returns node's HTML markup.
  • DOM.setOuterHTML: Sets node HTML markup, returns new node id.
  • DOM.performSearch: Searches for a given string in the DOM tree. Use getSearchResults to access search results or cancelSearch to end this search session.
  • DOM.getSearchResults: Returns search results from given fromIndex to given toIndex from the sarch with the given identifier.
  • DOM.discardSearchResults: Discards search results from the session with the given id. getSearchResults should no longer be called for that search.
  • DOM.requestNode: Requests that the node is sent to the caller given the JavaScript node object reference. All nodes that form the path from the node to the root are also sent to the client as a series of setChildNodes notifications.
  • DOM.setInspectModeEnabled: Enters the 'inspect' mode. In this mode, elements that user is hovering over are highlighted. Backend then generates 'inspectNodeRequested' event upon element selection.
  • DOM.highlightRect: Highlights given rectangle. Coordinates are absolute with respect to the main frame viewport.
  • DOM.highlightQuad: Highlights given quad. Coordinates are absolute with respect to the main frame viewport.
  • DOM.highlightNode: Highlights DOM node with given id or with the given JavaScript object wrapper. Either nodeId or objectId must be specified.
  • DOM.hideHighlight: Hides DOM node highlight.
  • DOM.highlightFrame: Highlights owner element of the frame with given id.
  • DOM.pushNodeByPathToFrontend: Requests that the node is sent to the caller given its path. // FIXME, use XPath
  • DOM.pushNodeByBackendIdToFrontend: Requests that the node is sent to the caller given its backend node id.
  • DOM.releaseBackendNodeIds: Requests that group of BackendNodeIds is released.
  • DOM.resolveNode: Resolves JavaScript node object for given node id.
  • DOM.getAttributes: Returns attributes for the specified node.
  • DOM.moveTo: Moves node into the new container, places it before the given anchor.
  • DOM.undo: Undoes the last performed action.
  • DOM.redo: Re-does the last undone action.
  • DOM.markUndoableState: Marks last undoable state.
  • DOM.focus: Focuses the given element.
  • DOM.setFileInputFiles: Sets files for the given file input element.
  • DOM.getBoxModel: Returns boxes for the currently selected nodes.
  • DOM.getNodeForLocation: Returns node id at given location.
Event

DOM.NodeId Type

Unique DOM node identifier.

Integer

DOM.BackendNodeId Type

Unique DOM node identifier used to reference a node that may not have been pushed to the front-end.

Integer

DOM.PseudoType Type

Pseudo element type.

( before | after )

DOM.Node Type

DOM interaction is implemented in terms of mirror objects that represent the actual DOM nodes. DOMNode is a base node mirror type.

nodeId
DOM.NodeId Node identifier that is passed into the rest of the DOM messages as the nodeId. Backend will only push node with given id once. It is aware of all requested nodes and will only fire DOM events for nodes known to the client.
nodeType
Integer Node's nodeType.
nodeName
String Node's nodeName.
localName
String Node's localName.
nodeValue
String Node's nodeValue.
childNodeCount (optional)
Integer Child count for Container nodes.
children (optional)
[DOM.Node] Child nodes of this node when requested with children.
attributes (optional)
[String] Attributes of the Element node in the form of flat array [name1, value1, name2, value2].
documentURL (optional)
String Document URL that Document or FrameOwner node points to.
baseURL (optional)
String Base URL that Document or FrameOwner node uses for URL completion.
publicId (optional)
String DocumentType's publicId.
systemId (optional)
String DocumentType's systemId.
internalSubset (optional)
String DocumentType's internalSubset.
xmlVersion (optional)
String Document's XML version in case of XML documents.
name (optional)
String Attr's name.
value (optional)
String Attr's value.
pseudoType (optional)
DOM.PseudoType Pseudo element type for this node.
frameId (optional)
Page.FrameId Frame ID for frame owner elements.
contentDocument (optional)
DOM.Node Content document for frame owner elements.
shadowRoots (optional)
[DOM.Node] Shadow root list for given element host.
templateContent (optional)
DOM.Node Content document fragment for template elements.
pseudoElements (optional)
[DOM.Node] Pseudo elements associated with this node.

DOM.EventListener Type

DOM interaction is implemented in terms of mirror objects that represent the actual DOM nodes. DOMNode is a base node mirror type.

type
String EventListener's type.
useCapture
Boolean EventListener's useCapture.
isAttribute
Boolean EventListener's isAttribute.
nodeId
DOM.NodeId Target DOMNode id.
handlerBody
String Event handler function body.
location (optional)
Debugger.Location Handler code location.
sourceName (optional)
String Source script URL.
handler (optional)
Runtime.RemoteObject Event handler function value.

DOM.RGBA Type

A structure holding an RGBA color.

r
Integer The red component, in the [0-255] range.
g
Integer The green component, in the [0-255] range.
b
Integer The blue component, in the [0-255] range.
a (optional)
Number The alpha component, in the [0-1] range (default: 1).

DOM.Quad Type

An array of quad vertices, x immediately followed by y for each point, points clock-wise.

[Number]

DOM.BoxModel Type

Box model.

content
DOM.Quad Content box
padding
DOM.Quad Padding box
border
DOM.Quad Border box
margin
DOM.Quad Margin box
width
Integer Node width
height
Integer Node height

DOM.Rect Type

Rectangle.

x
Number X coordinate
y
Number Y coordinate
width
Number Rectangle width
height
Number Rectangle height

DOM.HighlightConfig Type

Configuration data for the highlighting of page elements.

showInfo (optional)
Boolean Whether the node info tooltip should be shown (default: false).
contentColor (optional)
DOM.RGBA The content box highlight fill color (default: transparent).
paddingColor (optional)
DOM.RGBA The padding highlight fill color (default: transparent).
borderColor (optional)
DOM.RGBA The border highlight fill color (default: transparent).
marginColor (optional)
DOM.RGBA The margin highlight fill color (default: transparent).
eventTargetColor (optional)
DOM.RGBA The event target element highlight fill color (default: transparent).

DOM.enable Command

Enables DOM agent for the given page.

Code Example:

// WebInspector Command: DOM.enable
DOM.enable();

DOM.disable Command

Disables DOM agent for the given page.

Code Example:

// WebInspector Command: DOM.disable
DOM.disable();

DOM.getDocument Command

Returns the root DOM node to the caller.

Callback Parameters:

root
DOM.Node Resulting node.

Code Example:

// WebInspector Command: DOM.getDocument
DOM.getDocument(function callback(res) {
	// res = {root}
});

DOM.requestChildNodes Command

Requests that children of the node with given id are returned to the caller in form of setChildNodes events where not only immediate children are retrieved, but all children down to the specified depth.

nodeId
DOM.NodeId Id of the node to get children for.
depth (optional)
Integer The maximum depth at which children should be retrieved, defaults to 1. Use -1 for the entire subtree or provide an integer larger than 0.

Code Example:

// WebInspector Command: DOM.requestChildNodes
DOM.requestChildNodes(nodeId, depth);

DOM.querySelector Command

Executes querySelector on a given node.

nodeId
DOM.NodeId Id of the node to query upon.
selector
String Selector string.

Callback Parameters:

nodeId
DOM.NodeId Query selector result.

Code Example:

// WebInspector Command: DOM.querySelector
DOM.querySelector(nodeId, selector, function callback(res) {
	// res = {nodeId}
});

DOM.querySelectorAll Command

Executes querySelectorAll on a given node.

nodeId
DOM.NodeId Id of the node to query upon.
selector
String Selector string.

Callback Parameters:

nodeIds
[DOM.NodeId] Query selector result.

Code Example:

// WebInspector Command: DOM.querySelectorAll
DOM.querySelectorAll(nodeId, selector, function callback(res) {
	// res = {nodeIds}
});

DOM.setNodeName Command

Sets node name for a node with given id.

nodeId
DOM.NodeId Id of the node to set name for.
name
String New node's name.

Callback Parameters:

nodeId
DOM.NodeId New node's id.

Code Example:

// WebInspector Command: DOM.setNodeName
DOM.setNodeName(nodeId, name, function callback(res) {
	// res = {nodeId}
});

DOM.setNodeValue Command

Sets node value for a node with given id.

nodeId
DOM.NodeId Id of the node to set value for.
value
String New node's value.

Code Example:

// WebInspector Command: DOM.setNodeValue
DOM.setNodeValue(nodeId, value);

DOM.removeNode Command

Removes node with given id.

nodeId
DOM.NodeId Id of the node to remove.

Code Example:

// WebInspector Command: DOM.removeNode
DOM.removeNode(nodeId);

DOM.setAttributeValue Command

Sets attribute for an element with given id.

nodeId
DOM.NodeId Id of the element to set attribute for.
name
String Attribute name.
value
String Attribute value.

Code Example:

// WebInspector Command: DOM.setAttributeValue
DOM.setAttributeValue(nodeId, name, value);

DOM.setAttributesAsText Command

Sets attributes on element with given id. This method is useful when user edits some existing attribute value and types in several attribute name/value pairs.

nodeId
DOM.NodeId Id of the element to set attributes for.
text
String Text with a number of attributes. Will parse this text using HTML parser.
name (optional)
String Attribute name to replace with new attributes derived from text in case text parsed successfully.

Code Example:

// WebInspector Command: DOM.setAttributesAsText
DOM.setAttributesAsText(nodeId, text, name);

DOM.removeAttribute Command

Removes attribute with given name from an element with given id.

nodeId
DOM.NodeId Id of the element to remove attribute from.
name
String Name of the attribute to remove.

Code Example:

// WebInspector Command: DOM.removeAttribute
DOM.removeAttribute(nodeId, name);

DOM.getEventListenersForNode Command

Returns event listeners relevant to the node.

nodeId
DOM.NodeId Id of the node to get listeners for.
objectGroup (optional)
String Symbolic group name for handler value. Handler value is not returned without this parameter specified.

Callback Parameters:

listeners
[DOM.EventListener] Array of relevant listeners.

Code Example:

// WebInspector Command: DOM.getEventListenersForNode
DOM.getEventListenersForNode(nodeId, objectGroup, function callback(res) {
	// res = {listeners}
});

DOM.getOuterHTML Command

Returns node's HTML markup.

nodeId
DOM.NodeId Id of the node to get markup for.

Callback Parameters:

outerHTML
String Outer HTML markup.

Code Example:

// WebInspector Command: DOM.getOuterHTML
DOM.getOuterHTML(nodeId, function callback(res) {
	// res = {outerHTML}
});

DOM.setOuterHTML Command

Sets node HTML markup, returns new node id.

nodeId
DOM.NodeId Id of the node to set markup for.
outerHTML
String Outer HTML markup to set.

Code Example:

// WebInspector Command: DOM.setOuterHTML
DOM.setOuterHTML(nodeId, outerHTML);

DOM.performSearch Command

Searches for a given string in the DOM tree. Use getSearchResults to access search results or cancelSearch to end this search session.

query
String Plain text or query selector or XPath search query.

Callback Parameters:

searchId
String Unique search session identifier.
resultCount
Integer Number of search results.

Code Example:

// WebInspector Command: DOM.performSearch
DOM.performSearch(query, function callback(res) {
	// res = {searchId, resultCount}
});

DOM.getSearchResults Command

Returns search results from given fromIndex to given toIndex from the sarch with the given identifier.

searchId
String Unique search session identifier.
fromIndex
Integer Start index of the search result to be returned.
toIndex
Integer End index of the search result to be returned.

Callback Parameters:

nodeIds
[DOM.NodeId] Ids of the search result nodes.

Code Example:

// WebInspector Command: DOM.getSearchResults
DOM.getSearchResults(searchId, fromIndex, toIndex, function callback(res) {
	// res = {nodeIds}
});

DOM.discardSearchResults Command

Discards search results from the session with the given id. getSearchResults should no longer be called for that search.

searchId
String Unique search session identifier.

Code Example:

// WebInspector Command: DOM.discardSearchResults
DOM.discardSearchResults(searchId);

DOM.requestNode Command

Requests that the node is sent to the caller given the JavaScript node object reference. All nodes that form the path from the node to the root are also sent to the client as a series of setChildNodes notifications.

objectId
Runtime.RemoteObjectId JavaScript object id to convert into node.

Callback Parameters:

nodeId
DOM.NodeId Node id for given object.

Code Example:

// WebInspector Command: DOM.requestNode
DOM.requestNode(objectId, function callback(res) {
	// res = {nodeId}
});

DOM.setInspectModeEnabled Command

Enters the 'inspect' mode. In this mode, elements that user is hovering over are highlighted. Backend then generates 'inspectNodeRequested' event upon element selection.

enabled
Boolean True to enable inspection mode, false to disable it.
inspectShadowDOM (optional)
Boolean True to enable inspection mode for shadow DOM.
highlightConfig (optional)
DOM.HighlightConfig A descriptor for the highlight appearance of hovered-over nodes. May be omitted if enabled == false.

Code Example:

// WebInspector Command: DOM.setInspectModeEnabled
DOM.setInspectModeEnabled(enabled, inspectShadowDOM, highlightConfig);

DOM.highlightRect Command

Highlights given rectangle. Coordinates are absolute with respect to the main frame viewport.

x
Integer X coordinate
y
Integer Y coordinate
width
Integer Rectangle width
height
Integer Rectangle height
color (optional)
DOM.RGBA The highlight fill color (default: transparent).
outlineColor (optional)
DOM.RGBA The highlight outline color (default: transparent).

Code Example:

// WebInspector Command: DOM.highlightRect
DOM.highlightRect(x, y, width, height, color, outlineColor);

DOM.highlightQuad Command

Highlights given quad. Coordinates are absolute with respect to the main frame viewport.

quad
DOM.Quad Quad to highlight
color (optional)
DOM.RGBA The highlight fill color (default: transparent).
outlineColor (optional)
DOM.RGBA The highlight outline color (default: transparent).

Code Example:

// WebInspector Command: DOM.highlightQuad
DOM.highlightQuad(quad, color, outlineColor);

DOM.highlightNode Command

Highlights DOM node with given id or with the given JavaScript object wrapper. Either nodeId or objectId must be specified.

highlightConfig
DOM.HighlightConfig A descriptor for the highlight appearance.
nodeId (optional)
DOM.NodeId Identifier of the node to highlight.
objectId (optional)
Runtime.RemoteObjectId JavaScript object id of the node to be highlighted.

Code Example:

// WebInspector Command: DOM.highlightNode
DOM.highlightNode(highlightConfig, nodeId, objectId);

DOM.hideHighlight Command

Hides DOM node highlight.

Code Example:

// WebInspector Command: DOM.hideHighlight
DOM.hideHighlight();

DOM.highlightFrame Command

Highlights owner element of the frame with given id.

frameId
Page.FrameId Identifier of the frame to highlight.
contentColor (optional)
DOM.RGBA The content box highlight fill color (default: transparent).
contentOutlineColor (optional)
DOM.RGBA The content box highlight outline color (default: transparent).

Code Example:

// WebInspector Command: DOM.highlightFrame
DOM.highlightFrame(frameId, contentColor, contentOutlineColor);

DOM.pushNodeByPathToFrontend Command

Requests that the node is sent to the caller given its path. // FIXME, use XPath

path
String Path to node in the proprietary format.

Callback Parameters:

nodeId
DOM.NodeId Id of the node for given path.

Code Example:

// WebInspector Command: DOM.pushNodeByPathToFrontend
DOM.pushNodeByPathToFrontend(path, function callback(res) {
	// res = {nodeId}
});

DOM.pushNodeByBackendIdToFrontend Command

Requests that the node is sent to the caller given its backend node id.

backendNodeId
DOM.BackendNodeId The backend node id of the node.

Callback Parameters:

nodeId
DOM.NodeId The pushed node's id.

Code Example:

// WebInspector Command: DOM.pushNodeByBackendIdToFrontend
DOM.pushNodeByBackendIdToFrontend(backendNodeId, function callback(res) {
	// res = {nodeId}
});

DOM.releaseBackendNodeIds Command

Requests that group of BackendNodeIds is released.

nodeGroup
String The backend node ids group name.

Code Example:

// WebInspector Command: DOM.releaseBackendNodeIds
DOM.releaseBackendNodeIds(nodeGroup);

DOM.resolveNode Command

Resolves JavaScript node object for given node id.

nodeId
DOM.NodeId Id of the node to resolve.
objectGroup (optional)
String Symbolic group name that can be used to release multiple objects.

Callback Parameters:

object
Runtime.RemoteObject JavaScript object wrapper for given node.

Code Example:

// WebInspector Command: DOM.resolveNode
DOM.resolveNode(nodeId, objectGroup, function callback(res) {
	// res = {object}
});

DOM.getAttributes Command

Returns attributes for the specified node.

nodeId
DOM.NodeId Id of the node to retrieve attibutes for.

Callback Parameters:

attributes
[String] An interleaved array of node attribute names and values.

Code Example:

// WebInspector Command: DOM.getAttributes
DOM.getAttributes(nodeId, function callback(res) {
	// res = {attributes}
});

DOM.moveTo Command

Moves node into the new container, places it before the given anchor.

nodeId
DOM.NodeId Id of the node to drop.
targetNodeId
DOM.NodeId Id of the element to drop into.
insertBeforeNodeId (optional)
DOM.NodeId Drop node before given one.

Callback Parameters:

nodeId
DOM.NodeId New id of the moved node.

Code Example:

// WebInspector Command: DOM.moveTo
DOM.moveTo(nodeId, targetNodeId, insertBeforeNodeId, function callback(res) {
	// res = {nodeId}
});

DOM.undo Command

Undoes the last performed action.

Code Example:

// WebInspector Command: DOM.undo
DOM.undo();

DOM.redo Command

Re-does the last undone action.

Code Example:

// WebInspector Command: DOM.redo
DOM.redo();

DOM.markUndoableState Command

Marks last undoable state.

Code Example:

// WebInspector Command: DOM.markUndoableState
DOM.markUndoableState();

DOM.focus Command

Focuses the given element.

nodeId
DOM.NodeId Id of the node to focus.

Code Example:

// WebInspector Command: DOM.focus
DOM.focus(nodeId);

DOM.setFileInputFiles Command

Sets files for the given file input element.

nodeId
DOM.NodeId Id of the file input node to set files for.
files
[String] Array of file paths to set.

Code Example:

// WebInspector Command: DOM.setFileInputFiles
DOM.setFileInputFiles(nodeId, files);

DOM.getBoxModel Command

Returns boxes for the currently selected nodes.

nodeId
DOM.NodeId Id of the node to get box model for.

Callback Parameters:

model
DOM.BoxModel Box model for the node.

Code Example:

// WebInspector Command: DOM.getBoxModel
DOM.getBoxModel(nodeId, function callback(res) {
	// res = {model}
});

DOM.getNodeForLocation Command

Returns node id at given location.

x
Integer X coordinate.
y
Integer Y coordinate.

Callback Parameters:

nodeId
DOM.NodeId Id of the node at given coordinates.

Code Example:

// WebInspector Command: DOM.getNodeForLocation
DOM.getNodeForLocation(x, y, function callback(res) {
	// res = {nodeId}
});

DOM.documentUpdated Event

Fired when Document has been totally updated. Node ids are no longer valid.

Code Example:

// WebInspector Event: DOM.documentUpdated
function onDocumentUpdated(res) {
	// res = {}
}

DOM.inspectNodeRequested Event

Fired when the node should be inspected. This happens after call to setInspectModeEnabled.

nodeId
DOM.NodeId Id of the node to inspect.

Code Example:

// WebInspector Event: DOM.inspectNodeRequested
function onInspectNodeRequested(res) {
	// res = {nodeId}
}

DOM.setChildNodes Event

Fired when backend wants to provide client with the missing DOM structure. This happens upon most of the calls requesting node ids.

parentId
DOM.NodeId Parent node id to populate with children.
nodes
[DOM.Node] Child nodes array.

Code Example:

// WebInspector Event: DOM.setChildNodes
function onSetChildNodes(res) {
	// res = {parentId, nodes}
}

DOM.attributeModified Event

Fired when Element's attribute is modified.

nodeId
DOM.NodeId Id of the node that has changed.
name
String Attribute name.
value
String Attribute value.

Code Example:

// WebInspector Event: DOM.attributeModified
function onAttributeModified(res) {
	// res = {nodeId, name, value}
}

DOM.attributeRemoved Event

Fired when Element's attribute is removed.

nodeId
DOM.NodeId Id of the node that has changed.
name
String A ttribute name.

Code Example:

// WebInspector Event: DOM.attributeRemoved
function onAttributeRemoved(res) {
	// res = {nodeId, name}
}

DOM.inlineStyleInvalidated Event

Fired when Element's inline style is modified via a CSS property modification.

nodeIds
[DOM.NodeId] Ids of the nodes for which the inline styles have been invalidated.

Code Example:

// WebInspector Event: DOM.inlineStyleInvalidated
function onInlineStyleInvalidated(res) {
	// res = {nodeIds}
}

DOM.characterDataModified Event

Mirrors DOMCharacterDataModified event.

nodeId
DOM.NodeId Id of the node that has changed.
characterData
String New text value.

Code Example:

// WebInspector Event: DOM.characterDataModified
function onCharacterDataModified(res) {
	// res = {nodeId, characterData}
}

DOM.childNodeCountUpdated Event

Fired when Container's child node count has changed.

nodeId
DOM.NodeId Id of the node that has changed.
childNodeCount
Integer New node count.

Code Example:

// WebInspector Event: DOM.childNodeCountUpdated
function onChildNodeCountUpdated(res) {
	// res = {nodeId, childNodeCount}
}

DOM.childNodeInserted Event

Mirrors DOMNodeInserted event.

parentNodeId
DOM.NodeId Id of the node that has changed.
previousNodeId
DOM.NodeId If of the previous siblint.
node
DOM.Node Inserted node data.

Code Example:

// WebInspector Event: DOM.childNodeInserted
function onChildNodeInserted(res) {
	// res = {parentNodeId, previousNodeId, node}
}

DOM.childNodeRemoved Event

Mirrors DOMNodeRemoved event.

parentNodeId
DOM.NodeId Parent id.
nodeId
DOM.NodeId Id of the node that has been removed.

Code Example:

// WebInspector Event: DOM.childNodeRemoved
function onChildNodeRemoved(res) {
	// res = {parentNodeId, nodeId}
}

DOM.shadowRootPushed Event

Called when shadow root is pushed into the element.

hostId
DOM.NodeId Host element id.
root
DOM.Node Shadow root.

Code Example:

// WebInspector Event: DOM.shadowRootPushed
function onShadowRootPushed(res) {
	// res = {hostId, root}
}

DOM.shadowRootPopped Event

Called when shadow root is popped from the element.

hostId
DOM.NodeId Host element id.
rootId
DOM.NodeId Shadow root id.

Code Example:

// WebInspector Event: DOM.shadowRootPopped
function onShadowRootPopped(res) {
	// res = {hostId, rootId}
}

DOM.pseudoElementAdded Event

Called when a pseudo element is added to an element.

parentId
DOM.NodeId Pseudo element's parent element id.
pseudoElement
DOM.Node The added pseudo element.

Code Example:

// WebInspector Event: DOM.pseudoElementAdded
function onPseudoElementAdded(res) {
	// res = {parentId, pseudoElement}
}

DOM.pseudoElementRemoved Event

Called when a pseudo element is removed from an element.

parentId
DOM.NodeId Pseudo element's parent element id.
pseudoElementId
DOM.NodeId The removed pseudo element id.

Code Example:

// WebInspector Event: DOM.pseudoElementRemoved
function onPseudoElementRemoved(res) {
	// res = {parentId, pseudoElementId}
}

DOMDebugger

DOM debugging allows setting breakpoints on particular DOM operations and events. JavaScript execution will stop on these operations as if there was a regular breakpoint set.

Type Command

DOMDebugger.DOMBreakpointType Type

DOM breakpoint type.

( subtree-modified | attribute-modified | node-removed )

DOMDebugger.setDOMBreakpoint Command

Sets breakpoint on particular operation with DOM.

nodeId
DOM.NodeId Identifier of the node to set breakpoint on.
type
DOMDebugger.DOMBreakpointType Type of the operation to stop upon.

Code Example:

// WebInspector Command: DOMDebugger.setDOMBreakpoint
DOMDebugger.setDOMBreakpoint(nodeId, type);

DOMDebugger.removeDOMBreakpoint Command

Removes DOM breakpoint that was set using setDOMBreakpoint.

nodeId
DOM.NodeId Identifier of the node to remove breakpoint from.
type
DOMDebugger.DOMBreakpointType Type of the breakpoint to remove.

Code Example:

// WebInspector Command: DOMDebugger.removeDOMBreakpoint
DOMDebugger.removeDOMBreakpoint(nodeId, type);

DOMDebugger.setEventListenerBreakpoint Command

Sets breakpoint on particular DOM event.

eventName
String DOM Event name to stop on (any DOM event will do).

Code Example:

// WebInspector Command: DOMDebugger.setEventListenerBreakpoint
DOMDebugger.setEventListenerBreakpoint(eventName);

DOMDebugger.removeEventListenerBreakpoint Command

Removes breakpoint on particular DOM event.

eventName
String Event name.

Code Example:

// WebInspector Command: DOMDebugger.removeEventListenerBreakpoint
DOMDebugger.removeEventListenerBreakpoint(eventName);

DOMDebugger.setInstrumentationBreakpoint Command

Sets breakpoint on particular native event.

eventName
String Instrumentation name to stop on.

Code Example:

// WebInspector Command: DOMDebugger.setInstrumentationBreakpoint
DOMDebugger.setInstrumentationBreakpoint(eventName);

DOMDebugger.removeInstrumentationBreakpoint Command

Removes breakpoint on particular native event.

eventName
String Instrumentation name to stop on.

Code Example:

// WebInspector Command: DOMDebugger.removeInstrumentationBreakpoint
DOMDebugger.removeInstrumentationBreakpoint(eventName);

DOMDebugger.setXHRBreakpoint Command

Sets breakpoint on XMLHttpRequest.

url
String Resource URL substring. All XHRs having this substring in the URL will get stopped upon.

Code Example:

// WebInspector Command: DOMDebugger.setXHRBreakpoint
DOMDebugger.setXHRBreakpoint(url);

DOMDebugger.removeXHRBreakpoint Command

Removes breakpoint from XMLHttpRequest.

url
String Resource URL substring.

Code Example:

// WebInspector Command: DOMDebugger.removeXHRBreakpoint
DOMDebugger.removeXHRBreakpoint(url);

DOMStorage

Query and modify DOM storage.

Type Command Event

DOMStorage.StorageId Type

DOM Storage identifier.

securityOrigin
String Security origin for the storage.
isLocalStorage
Boolean Whether the storage is local storage (not session storage).

DOMStorage.Item Type

DOM Storage item.

[String]

DOMStorage.enable Command

Enables storage tracking, storage events will now be delivered to the client.

Code Example:

// WebInspector Command: DOMStorage.enable
DOMStorage.enable();

DOMStorage.disable Command

Disables storage tracking, prevents storage events from being sent to the client.

Code Example:

// WebInspector Command: DOMStorage.disable
DOMStorage.disable();

DOMStorage.getDOMStorageItems Command

storageId
DOMStorage.StorageId

Callback Parameters:

entries
[DOMStorage.Item]

Code Example:

// WebInspector Command: DOMStorage.getDOMStorageItems
DOMStorage.getDOMStorageItems(storageId, function callback(res) {
	// res = {entries}
});

DOMStorage.setDOMStorageItem Command

storageId
DOMStorage.StorageId
key
String
value
String

Code Example:

// WebInspector Command: DOMStorage.setDOMStorageItem
DOMStorage.setDOMStorageItem(storageId, key, value);

DOMStorage.removeDOMStorageItem Command

storageId
DOMStorage.StorageId
key
String

Code Example:

// WebInspector Command: DOMStorage.removeDOMStorageItem
DOMStorage.removeDOMStorageItem(storageId, key);

DOMStorage.domStorageItemsCleared Event

storageId
DOMStorage.StorageId

Code Example:

// WebInspector Event: DOMStorage.domStorageItemsCleared
function onDomStorageItemsCleared(res) {
	// res = {storageId}
}

DOMStorage.domStorageItemRemoved Event

storageId
DOMStorage.StorageId
key
String

Code Example:

// WebInspector Event: DOMStorage.domStorageItemRemoved
function onDomStorageItemRemoved(res) {
	// res = {storageId, key}
}

DOMStorage.domStorageItemAdded Event

storageId
DOMStorage.StorageId
key
String
newValue
String

Code Example:

// WebInspector Event: DOMStorage.domStorageItemAdded
function onDomStorageItemAdded(res) {
	// res = {storageId, key, newValue}
}

DOMStorage.domStorageItemUpdated Event

storageId
DOMStorage.StorageId
key
String
oldValue
String
newValue
String

Code Example:

// WebInspector Event: DOMStorage.domStorageItemUpdated
function onDomStorageItemUpdated(res) {
	// res = {storageId, key, oldValue, newValue}
}

Database

Type Command Event

Database.DatabaseId Type

Unique identifier of Database object.

String

Database.Database Type

Database object.

id
Database.DatabaseId Database ID.
domain
String Database domain.
name
String Database name.
version
String Database version.

Database.Error Type

Database error.

message
String Error message.
code
Integer Error code.

Database.enable Command

Enables database tracking, database events will now be delivered to the client.

Code Example:

// WebInspector Command: Database.enable
Database.enable();

Database.disable Command

Disables database tracking, prevents database events from being sent to the client.

Code Example:

// WebInspector Command: Database.disable
Database.disable();

Database.getDatabaseTableNames Command

databaseId
Database.DatabaseId

Callback Parameters:

tableNames
[String]

Code Example:

// WebInspector Command: Database.getDatabaseTableNames
Database.getDatabaseTableNames(databaseId, function callback(res) {
	// res = {tableNames}
});

Database.executeSQL Command

databaseId
Database.DatabaseId
query
String

Callback Parameters:

columnNames (optional)
[String]
values (optional)
[Any]
sqlError (optional)
Database.Error

Code Example:

// WebInspector Command: Database.executeSQL
Database.executeSQL(databaseId, query, function callback(res) {
	// res = {columnNames, values, sqlError}
});

Database.addDatabase Event

database
Database.Database

Code Example:

// WebInspector Event: Database.addDatabase
function onAddDatabase(res) {
	// res = {database}
}

Debugger

Debugger domain exposes JavaScript debugging capabilities. It allows setting and removing breakpoints, stepping through execution, exploring stack traces, etc.

Type Command Event

Debugger.BreakpointId Type

Breakpoint identifier.

String

Debugger.ScriptId Type

Unique script identifier.

String

Debugger.CallFrameId Type

Call frame identifier.

String

Debugger.Location Type

Location in the source code.

scriptId
Debugger.ScriptId Script identifier as reported in the Debugger.scriptParsed.
lineNumber
Integer Line number in the script (0-based).
columnNumber (optional)
Integer Column number in the script (0-based).

Debugger.FunctionDetails Type

Information about the function.

location
Debugger.Location Location of the function.
name (optional)
String Name of the function. Not present for anonymous functions.
displayName (optional)
String Display name of the function(specified in 'displayName' property on the function object).
inferredName (optional)
String Name of the function inferred from its initial assignment.
scopeChain (optional)
[Debugger.Scope] Scope chain for this closure.

Debugger.CallFrame Type

JavaScript call frame. Array of call frames form the call stack.

callFrameId
Debugger.CallFrameId Call frame identifier. This identifier is only valid while the virtual machine is paused.
functionName
String Name of the JavaScript function called on this call frame.
location
Debugger.Location Location in the source code.
scopeChain
[Debugger.Scope] Scope chain for this call frame.
this
Runtime.RemoteObject this object for this call frame.

Debugger.Scope Type

Scope description.

type
( global | local | with | closure | catch ) Scope type.
object
Runtime.RemoteObject Object representing the scope. For global and with scopes it represents the actual object; for the rest of the scopes, it is artificial transient object enumerating scope variables as its properties.

Debugger.SetScriptSourceError Type

Error data for setScriptSource command. compileError is a case type for uncompilable script source error.

compileError (optional)
Object

Debugger.enable Command

Enables debugger for the given page. Clients should not assume that the debugging has been enabled until the result for this command is received.

Code Example:

// WebInspector Command: Debugger.enable
Debugger.enable();

Debugger.disable Command

Disables debugger for given page.

Code Example:

// WebInspector Command: Debugger.disable
Debugger.disable();

Debugger.setBreakpointsActive Command

Activates / deactivates all breakpoints on the page.

active
Boolean New value for breakpoints active state.

Code Example:

// WebInspector Command: Debugger.setBreakpointsActive
Debugger.setBreakpointsActive(active);

Debugger.setSkipAllPauses Command

Makes page not interrupt on any pauses (breakpoint, exception, dom exception etc).

skipped
Boolean New value for skip pauses state.
untilReload (optional)
Boolean Whether page reload should set skipped to false.

Code Example:

// WebInspector Command: Debugger.setSkipAllPauses
Debugger.setSkipAllPauses(skipped, untilReload);

Debugger.setBreakpointByUrl Command

Sets JavaScript breakpoint at given location specified either by URL or URL regex. Once this command is issued, all existing parsed scripts will have breakpoints resolved and returned in locations property. Further matching script parsing will result in subsequent breakpointResolved events issued. This logical breakpoint will survive page reloads.

lineNumber
Integer Line number to set breakpoint at.
url (optional)
String URL of the resources to set breakpoint on.
urlRegex (optional)
String Regex pattern for the URLs of the resources to set breakpoints on. Either url or urlRegex must be specified.
columnNumber (optional)
Integer Offset in the line to set breakpoint at.
condition (optional)
String Expression to use as a breakpoint condition. When specified, debugger will only stop on the breakpoint if this expression evaluates to true.
isAntibreakpoint (optional)
Boolean Creates pseudo-breakpoint that prevents debugger from pausing on exception at this location.

Callback Parameters:

breakpointId
Debugger.BreakpointId Id of the created breakpoint for further reference.
locations
[Debugger.Location] List of the locations this breakpoint resolved into upon addition.

Code Example:

// WebInspector Command: Debugger.setBreakpointByUrl
Debugger.setBreakpointByUrl(lineNumber, url, urlRegex, columnNumber, condition, isAntibreakpoint, function callback(res) {
	// res = {breakpointId, locations}
});

Debugger.setBreakpoint Command

Sets JavaScript breakpoint at a given location.

location
Debugger.Location Location to set breakpoint in.
condition (optional)
String Expression to use as a breakpoint condition. When specified, debugger will only stop on the breakpoint if this expression evaluates to true.

Callback Parameters:

breakpointId
Debugger.BreakpointId Id of the created breakpoint for further reference.
actualLocation
Debugger.Location Location this breakpoint resolved into.

Code Example:

// WebInspector Command: Debugger.setBreakpoint
Debugger.setBreakpoint(location, condition, function callback(res) {
	// res = {breakpointId, actualLocation}
});

Debugger.removeBreakpoint Command

Removes JavaScript breakpoint.

breakpointId
Debugger.BreakpointId

Code Example:

// WebInspector Command: Debugger.removeBreakpoint
Debugger.removeBreakpoint(breakpointId);

Debugger.continueToLocation Command

Continues execution until specific location is reached.

location
Debugger.Location Location to continue to.
interstatementLocation (optional)
Boolean Allows breakpoints at the intemediate positions inside statements.

Code Example:

// WebInspector Command: Debugger.continueToLocation
Debugger.continueToLocation(location, interstatementLocation);

Debugger.stepOver Command

Steps over the statement.

Code Example:

// WebInspector Command: Debugger.stepOver
Debugger.stepOver();

Debugger.stepInto Command

Steps into the function call.

Code Example:

// WebInspector Command: Debugger.stepInto
Debugger.stepInto();

Debugger.stepOut Command

Steps out of the function call.

Code Example:

// WebInspector Command: Debugger.stepOut
Debugger.stepOut();

Debugger.pause Command

Stops on the next JavaScript statement.

Code Example:

// WebInspector Command: Debugger.pause
Debugger.pause();

Debugger.resume Command

Resumes JavaScript execution.

Code Example:

// WebInspector Command: Debugger.resume
Debugger.resume();

Debugger.searchInContent Command

Searches for given string in script content.

scriptId
Debugger.ScriptId Id of the script to search in.
query
String String to search for.
caseSensitive (optional)
Boolean If true, search is case sensitive.
isRegex (optional)
Boolean If true, treats string parameter as regex.

Callback Parameters:

result
[Page.SearchMatch] List of search matches.

Code Example:

// WebInspector Command: Debugger.searchInContent
Debugger.searchInContent(scriptId, query, caseSensitive, isRegex, function callback(res) {
	// res = {result}
});

Debugger.canSetScriptSource Command

Always returns true.

Callback Parameters:

result
Boolean True if setScriptSource is supported.

Code Example:

// WebInspector Command: Debugger.canSetScriptSource
Debugger.canSetScriptSource(function callback(res) {
	// res = {result}
});

Debugger.setScriptSource Command

Edits JavaScript source live.

scriptId
Debugger.ScriptId Id of the script to edit.
scriptSource
String New content of the script.
preview (optional)
Boolean If true the change will not actually be applied. Preview mode may be used to get result description without actually modifying the code.

Callback Parameters:

callFrames (optional)
[Debugger.CallFrame] New stack trace in case editing has happened while VM was stopped.
result (optional)
Object VM-specific description of the changes applied.

Code Example:

// WebInspector Command: Debugger.setScriptSource
Debugger.setScriptSource(scriptId, scriptSource, preview, function callback(res) {
	// res = {callFrames, result}
});

Debugger.restartFrame Command

Restarts particular call frame from the beginning.

callFrameId
Debugger.CallFrameId Call frame identifier to evaluate on.

Callback Parameters:

callFrames
[Debugger.CallFrame] New stack trace.
result
Object VM-specific description.

Code Example:

// WebInspector Command: Debugger.restartFrame
Debugger.restartFrame(callFrameId, function callback(res) {
	// res = {callFrames, result}
});

Debugger.getScriptSource Command

Returns source for the script with given id.

scriptId
Debugger.ScriptId Id of the script to get source for.

Callback Parameters:

scriptSource
String Script source.

Code Example:

// WebInspector Command: Debugger.getScriptSource
Debugger.getScriptSource(scriptId, function callback(res) {
	// res = {scriptSource}
});

Debugger.getFunctionDetails Command

Returns detailed informtation on given function.

functionId
Runtime.RemoteObjectId Id of the function to get location for.

Callback Parameters:

details
Debugger.FunctionDetails Information about the function.

Code Example:

// WebInspector Command: Debugger.getFunctionDetails
Debugger.getFunctionDetails(functionId, function callback(res) {
	// res = {details}
});

Debugger.setPauseOnExceptions Command

Defines pause on exceptions state. Can be set to stop on all exceptions, uncaught exceptions or no exceptions. Initial pause on exceptions state is none.

state
( none | uncaught | all ) Pause on exceptions mode.

Code Example:

// WebInspector Command: Debugger.setPauseOnExceptions
Debugger.setPauseOnExceptions(state);

Debugger.evaluateOnCallFrame Command

Evaluates expression on a given call frame.

callFrameId
Debugger.CallFrameId Call frame identifier to evaluate on.
expression
String Expression to evaluate.
objectGroup (optional)
String String object group name to put result into (allows rapid releasing resulting object handles using releaseObjectGroup).
includeCommandLineAPI (optional)
Boolean Specifies whether command line API should be available to the evaluated expression, defaults to false.
doNotPauseOnExceptionsAndMuteConsole (optional)
Boolean Specifies whether evaluation should stop on exceptions and mute console. Overrides setPauseOnException state.
returnByValue (optional)
Boolean Whether the result is expected to be a JSON object that should be sent by value.
generatePreview (optional)
Boolean Whether preview should be generated for the result.

Callback Parameters:

result
Runtime.RemoteObject Object wrapper for the evaluation result.
wasThrown (optional)
Boolean True if the result was thrown during the evaluation.

Code Example:

// WebInspector Command: Debugger.evaluateOnCallFrame
Debugger.evaluateOnCallFrame(callFrameId, expression, objectGroup, includeCommandLineAPI, doNotPauseOnExceptionsAndMuteConsole, returnByValue, generatePreview, function callback(res) {
	// res = {result, wasThrown}
});

Debugger.compileScript Command

Compiles expression.

expression
String Expression to compile.
sourceURL
String Source url to be set for the script.

Callback Parameters:

scriptId (optional)
Debugger.ScriptId Id of the script.
syntaxErrorMessage (optional)
String Syntax error message if compilation failed.

Code Example:

// WebInspector Command: Debugger.compileScript
Debugger.compileScript(expression, sourceURL, function callback(res) {
	// res = {scriptId, syntaxErrorMessage}
});

Debugger.runScript Command

Runs script with given id in a given context.

scriptId
Debugger.ScriptId Id of the script to run.
contextId (optional)
Runtime.ExecutionContextId Specifies in which isolated context to perform script run. Each content script lives in an isolated context and this parameter may be used to specify one of those contexts. If the parameter is omitted or 0 the evaluation will be performed in the context of the inspected page.
objectGroup (optional)
String Symbolic group name that can be used to release multiple objects.
doNotPauseOnExceptionsAndMuteConsole (optional)
Boolean Specifies whether script run should stop on exceptions and mute console. Overrides setPauseOnException state.

Callback Parameters:

result
Runtime.RemoteObject Run result.
wasThrown (optional)
Boolean True if the result was thrown during the script run.

Code Example:

// WebInspector Command: Debugger.runScript
Debugger.runScript(scriptId, contextId, objectGroup, doNotPauseOnExceptionsAndMuteConsole, function callback(res) {
	// res = {result, wasThrown}
});

Debugger.setOverlayMessage Command

Sets overlay message.

message (optional)
String Overlay message to display when paused in debugger.

Code Example:

// WebInspector Command: Debugger.setOverlayMessage
Debugger.setOverlayMessage(message);

Debugger.setVariableValue Command

Changes value of variable in a callframe or a closure. Either callframe or function must be specified. Object-based scopes are not supported and must be mutated manually.

scopeNumber
Integer 0-based number of scope as was listed in scope chain. Only 'local', 'closure' and 'catch' scope types are allowed. Other scopes could be manipulated manually.
variableName
String Variable name.
newValue
Runtime.CallArgument New variable value.
callFrameId (optional)
Debugger.CallFrameId Id of callframe that holds variable.
functionObjectId (optional)
Runtime.RemoteObjectId Object id of closure (function) that holds variable.

Code Example:

// WebInspector Command: Debugger.setVariableValue
Debugger.setVariableValue(scopeNumber, variableName, newValue, callFrameId, functionObjectId);

Debugger.getStepInPositions Command

Lists all positions where step-in is possible for a current statement in a specified call frame

callFrameId
Debugger.CallFrameId Id of a call frame where the current statement should be analized

Callback Parameters:

stepInPositions (optional)
[Debugger.Location] experimental

Code Example:

// WebInspector Command: Debugger.getStepInPositions
Debugger.getStepInPositions(callFrameId, function callback(res) {
	// res = {stepInPositions}
});

Debugger.getBacktrace Command

Returns call stack including variables changed since VM was paused. VM must be paused.

Callback Parameters:

callFrames
[Debugger.CallFrame] Call stack the virtual machine stopped on.

Code Example:

// WebInspector Command: Debugger.getBacktrace
Debugger.getBacktrace(function callback(res) {
	// res = {callFrames}
});

Debugger.skipStackFrames Command

Makes backend skip steps in the sources with names matching given pattern. VM will try leave blacklisted scripts by performing 'step in' several times, finally resorting to 'step out' if unsuccessful.

script (optional)
String Regular expression defining the scripts to ignore while stepping.

Code Example:

// WebInspector Command: Debugger.skipStackFrames
Debugger.skipStackFrames(script);

Debugger.globalObjectCleared Event

Called when global has been cleared and debugger client should reset its state. Happens upon navigation or reload.

Code Example:

// WebInspector Event: Debugger.globalObjectCleared
function onGlobalObjectCleared(res) {
	// res = {}
}

Debugger.scriptParsed Event

Fired when virtual machine parses script. This event is also fired for all known and uncollected scripts upon enabling debugger.

scriptId
Debugger.ScriptId Identifier of the script parsed.
url
String URL or name of the script parsed (if any).
startLine
Integer Line offset of the script within the resource with given URL (for script tags).
startColumn
Integer Column offset of the script within the resource with given URL.
endLine
Integer Last line of the script.
endColumn
Integer Length of the last line of the script.
isContentScript (optional)
Boolean Determines whether this script is a user extension script.
sourceMapURL (optional)
String URL of source map associated with script (if any).
hasSourceURL (optional)
Boolean True, if this script has sourceURL.

Code Example:

// WebInspector Event: Debugger.scriptParsed
function onScriptParsed(res) {
	// res = {scriptId, url, startLine, startColumn, endLine, endColumn, isContentScript, sourceMapURL, hasSourceURL}
}

Debugger.scriptFailedToParse Event

Fired when virtual machine fails to parse the script.

url
String URL of the script that failed to parse.
scriptSource
String Source text of the script that failed to parse.
startLine
Integer Line offset of the script within the resource.
errorLine
Integer Line with error.
errorMessage
String Parse error message.

Code Example:

// WebInspector Event: Debugger.scriptFailedToParse
function onScriptFailedToParse(res) {
	// res = {url, scriptSource, startLine, errorLine, errorMessage}
}

Debugger.breakpointResolved Event

Fired when breakpoint is resolved to an actual script and location.

breakpointId
Debugger.BreakpointId Breakpoint unique identifier.
location
Debugger.Location Actual breakpoint location.

Code Example:

// WebInspector Event: Debugger.breakpointResolved
function onBreakpointResolved(res) {
	// res = {breakpointId, location}
}

Debugger.paused Event

Fired when the virtual machine stopped on breakpoint or exception or any other stop criteria.

callFrames
[Debugger.CallFrame] Call stack the virtual machine stopped on.
reason
( XHR | DOM | EventListener | exception | assert | CSPViolation | debugCommand | other ) Pause reason.
data (optional)
Object Object containing break-specific auxiliary properties.
hitBreakpoints (optional)
[String] Hit breakpoints IDs

Code Example:

// WebInspector Event: Debugger.paused
function onPaused(res) {
	// res = {callFrames, reason, data, hitBreakpoints}
}

Debugger.resumed Event

Fired when the virtual machine resumed execution.

Code Example:

// WebInspector Event: Debugger.resumed
function onResumed(res) {
	// res = {}
}

FileSystem

Type Command

FileSystem.Entry Type

Represents a browser side file or directory.

url
String filesystem: URL for the entry.
name
String The name of the file or directory.
isDirectory
Boolean True if the entry is a directory.
mimeType (optional)
String MIME type of the entry, available for a file only.
resourceType (optional)
Page.ResourceType ResourceType of the entry, available for a file only.
isTextFile (optional)
Boolean True if the entry is a text file.

FileSystem.Metadata Type

Represents metadata of a file or entry.

modificationTime
Number Modification time.
size
Number File size. This field is always zero for directories.

FileSystem.enable Command

Enables events from backend.

Code Example:

// WebInspector Command: FileSystem.enable
FileSystem.enable();

FileSystem.disable Command

Disables events from backend.

Code Example:

// WebInspector Command: FileSystem.disable
FileSystem.disable();

FileSystem.requestFileSystemRoot Command

Returns root directory of the FileSystem, if exists.

origin
String Security origin of requesting FileSystem. One of frames in current page needs to have this security origin.
type
( temporary | persistent ) FileSystem type of requesting FileSystem.

Callback Parameters:

errorCode
Integer 0, if no error. Otherwise, errorCode is set to FileError::ErrorCode value.
root (optional)
FileSystem.Entry Contains root of the requested FileSystem if the command completed successfully.

Code Example:

// WebInspector Command: FileSystem.requestFileSystemRoot
FileSystem.requestFileSystemRoot(origin, type, function callback(res) {
	// res = {errorCode, root}
});

FileSystem.requestDirectoryContent Command

Returns content of the directory.

url
String URL of the directory that the frontend is requesting to read from.

Callback Parameters:

errorCode
Integer 0, if no error. Otherwise, errorCode is set to FileError::ErrorCode value.
entries (optional)
[FileSystem.Entry] Contains all entries on directory if the command completed successfully.

Code Example:

// WebInspector Command: FileSystem.requestDirectoryContent
FileSystem.requestDirectoryContent(url, function callback(res) {
	// res = {errorCode, entries}
});

FileSystem.requestMetadata Command

Returns metadata of the entry.

url
String URL of the entry that the frontend is requesting to get metadata from.

Callback Parameters:

errorCode
Integer 0, if no error. Otherwise, errorCode is set to FileError::ErrorCode value.
metadata (optional)
FileSystem.Metadata Contains metadata of the entry if the command completed successfully.

Code Example:

// WebInspector Command: FileSystem.requestMetadata
FileSystem.requestMetadata(url, function callback(res) {
	// res = {errorCode, metadata}
});

FileSystem.requestFileContent Command

Returns content of the file. Result should be sliced into [start, end).

url
String URL of the file that the frontend is requesting to read from.
readAsText
Boolean True if the content should be read as text, otherwise the result will be returned as base64 encoded text.
start (optional)
Integer Specifies the start of range to read.
end (optional)
Integer Specifies the end of range to read exclusively.
charset (optional)
String Overrides charset of the content when content is served as text.

Callback Parameters:

errorCode
Integer 0, if no error. Otherwise, errorCode is set to FileError::ErrorCode value.
content (optional)
String Content of the file.
charset (optional)
String Charset of the content if it is served as text.

Code Example:

// WebInspector Command: FileSystem.requestFileContent
FileSystem.requestFileContent(url, readAsText, start, end, charset, function callback(res) {
	// res = {errorCode, content, charset}
});

FileSystem.deleteEntry Command

Deletes specified entry. If the entry is a directory, the agent deletes children recursively.

url
String URL of the entry to delete.

Callback Parameters:

errorCode
Integer 0, if no error. Otherwise errorCode is set to FileError::ErrorCode value.

Code Example:

// WebInspector Command: FileSystem.deleteEntry
FileSystem.deleteEntry(url, function callback(res) {
	// res = {errorCode}
});

HeapProfiler

Type Command Event

HeapProfiler.ProfileHeader Type

Profile header.

title
String Profile title.
uid
Integer Unique identifier of the profile.
maxJSObjectId (optional)
Integer Last seen JS object Id.

HeapProfiler.HeapSnapshotObjectId Type

Heap snashot object id.

String

HeapProfiler.getProfileHeaders Command

Callback Parameters:

headers
[HeapProfiler.ProfileHeader]

Code Example:

// WebInspector Command: HeapProfiler.getProfileHeaders
HeapProfiler.getProfileHeaders(function callback(res) {
	// res = {headers}
});

HeapProfiler.startTrackingHeapObjects Command

Code Example:

// WebInspector Command: HeapProfiler.startTrackingHeapObjects
HeapProfiler.startTrackingHeapObjects();

HeapProfiler.stopTrackingHeapObjects Command

Code Example:

// WebInspector Command: HeapProfiler.stopTrackingHeapObjects
HeapProfiler.stopTrackingHeapObjects();

HeapProfiler.getHeapSnapshot Command

uid
Integer

Code Example:

// WebInspector Command: HeapProfiler.getHeapSnapshot
HeapProfiler.getHeapSnapshot(uid);

HeapProfiler.removeProfile Command

uid
Integer

Code Example:

// WebInspector Command: HeapProfiler.removeProfile
HeapProfiler.removeProfile(uid);

HeapProfiler.clearProfiles Command

Code Example:

// WebInspector Command: HeapProfiler.clearProfiles
HeapProfiler.clearProfiles();

HeapProfiler.takeHeapSnapshot Command

reportProgress (optional)
Boolean If true 'reportHeapSnapshotProgress' events will be generated while snapshot is being taken.

Code Example:

// WebInspector Command: HeapProfiler.takeHeapSnapshot
HeapProfiler.takeHeapSnapshot(reportProgress);

HeapProfiler.collectGarbage Command

Code Example:

// WebInspector Command: HeapProfiler.collectGarbage
HeapProfiler.collectGarbage();

HeapProfiler.getObjectByHeapObjectId Command

objectId
HeapProfiler.HeapSnapshotObjectId
objectGroup (optional)
String Symbolic group name that can be used to release multiple objects.

Callback Parameters:

result
Runtime.RemoteObject Evaluation result.

Code Example:

// WebInspector Command: HeapProfiler.getObjectByHeapObjectId
HeapProfiler.getObjectByHeapObjectId(objectId, objectGroup, function callback(res) {
	// res = {result}
});

HeapProfiler.getHeapObjectId Command

objectId
Runtime.RemoteObjectId Identifier of the object to get heap object id for.

Callback Parameters:

heapSnapshotObjectId
HeapProfiler.HeapSnapshotObjectId Id of the heap snapshot object corresponding to the passed remote object id.

Code Example:

// WebInspector Command: HeapProfiler.getHeapObjectId
HeapProfiler.getHeapObjectId(objectId, function callback(res) {
	// res = {heapSnapshotObjectId}
});

HeapProfiler.addProfileHeader Event

header
HeapProfiler.ProfileHeader

Code Example:

// WebInspector Event: HeapProfiler.addProfileHeader
function onAddProfileHeader(res) {
	// res = {header}
}

HeapProfiler.addHeapSnapshotChunk Event

uid
Integer
chunk
String

Code Example:

// WebInspector Event: HeapProfiler.addHeapSnapshotChunk
function onAddHeapSnapshotChunk(res) {
	// res = {uid, chunk}
}

HeapProfiler.finishHeapSnapshot Event

uid
Integer

Code Example:

// WebInspector Event: HeapProfiler.finishHeapSnapshot
function onFinishHeapSnapshot(res) {
	// res = {uid}
}

HeapProfiler.resetProfiles Event

Code Example:

// WebInspector Event: HeapProfiler.resetProfiles
function onResetProfiles(res) {
	// res = {}
}

HeapProfiler.reportHeapSnapshotProgress Event

done
Integer
total
Integer

Code Example:

// WebInspector Event: HeapProfiler.reportHeapSnapshotProgress
function onReportHeapSnapshotProgress(res) {
	// res = {done, total}
}

HeapProfiler.lastSeenObjectId Event

If heap objects tracking has been started then backend regulary sends a current value for last seen object id and corresponding timestamp. If the were changes in the heap since last event then one or more heapStatsUpdate events will be sent before a new lastSeenObjectId event.

lastSeenObjectId
Integer
timestamp
Number

Code Example:

// WebInspector Event: HeapProfiler.lastSeenObjectId
function onLastSeenObjectId(res) {
	// res = {lastSeenObjectId, timestamp}
}

HeapProfiler.heapStatsUpdate Event

If heap objects tracking has been started then backend may send update for one or more fragments

statsUpdate
[Integer] An array of triplets. Each triplet describes a fragment. The first integer is the fragment index, the second integer is a total count of objects for the fragment, the third integer is a total size of the objects for the fragment.

Code Example:

// WebInspector Event: HeapProfiler.heapStatsUpdate
function onHeapStatsUpdate(res) {
	// res = {statsUpdate}
}

IndexedDB

Type Command

IndexedDB.DatabaseWithObjectStores Type

Database with an array of object stores.

name
String Database name.
version
String Deprecated string database version.
intVersion
Integer Integer database version.
objectStores
[IndexedDB.ObjectStore] Object stores in this database.

IndexedDB.ObjectStore Type

Object store.

name
String Object store name.
keyPath
IndexedDB.KeyPath Object store key path.
autoIncrement
Boolean If true, object store has auto increment flag set.
indexes
[IndexedDB.ObjectStoreIndex] Indexes in this object store.

IndexedDB.ObjectStoreIndex Type

Object store index.

name
String Index name.
keyPath
IndexedDB.KeyPath Index key path.
unique
Boolean If true, index is unique.
multiEntry
Boolean If true, index allows multiple entries for a key.

IndexedDB.Key Type

Key.

type
( number | string | date | array ) Key type.
number (optional)
Number Number value.
string (optional)
String String value.
date (optional)
Number Date value.
array (optional)
[IndexedDB.Key] Array value.

IndexedDB.KeyRange Type

Key range.

lower (optional)
IndexedDB.Key Lower bound.
upper (optional)
IndexedDB.Key Upper bound.
lowerOpen
Boolean If true lower bound is open.
upperOpen
Boolean If true upper bound is open.

IndexedDB.DataEntry Type

Data entry.

key
Runtime.RemoteObject Key.
primaryKey
Runtime.RemoteObject Primary key.
value
Runtime.RemoteObject Value.

IndexedDB.KeyPath Type

Key path.

type
( null | string | array ) Key path type.
string (optional)
String String value.
array (optional)
[String] Array value.

IndexedDB.enable Command

Enables events from backend.

Code Example:

// WebInspector Command: IndexedDB.enable
IndexedDB.enable();

IndexedDB.disable Command

Disables events from backend.

Code Example:

// WebInspector Command: IndexedDB.disable
IndexedDB.disable();

IndexedDB.requestDatabaseNames Command

Requests database names for given security origin.

securityOrigin
String Security origin.

Callback Parameters:

databaseNames
[String] Database names for origin.

Code Example:

// WebInspector Command: IndexedDB.requestDatabaseNames
IndexedDB.requestDatabaseNames(securityOrigin, function callback(res) {
	// res = {databaseNames}
});

IndexedDB.requestDatabase Command

Requests database with given name in given frame.

securityOrigin
String Security origin.
databaseName
String Database name.

Callback Parameters:

databaseWithObjectStores
IndexedDB.DatabaseWithObjectStores Database with an array of object stores.

Code Example:

// WebInspector Command: IndexedDB.requestDatabase
IndexedDB.requestDatabase(securityOrigin, databaseName, function callback(res) {
	// res = {databaseWithObjectStores}
});

IndexedDB.requestData Command

Requests data from object store or index.

securityOrigin
String Security origin.
databaseName
String Database name.
objectStoreName
String Object store name.
indexName
String Index name, empty string for object store data requests.
skipCount
Integer Number of records to skip.
pageSize
Integer Number of records to fetch.
keyRange (optional)
IndexedDB.KeyRange Key range.

Callback Parameters:

objectStoreDataEntries
[IndexedDB.DataEntry] Array of object store data entries.
hasMore
Boolean If true, there are more entries to fetch in the given range.

Code Example:

// WebInspector Command: IndexedDB.requestData
IndexedDB.requestData(securityOrigin, databaseName, objectStoreName, indexName, skipCount, pageSize, keyRange, function callback(res) {
	// res = {objectStoreDataEntries, hasMore}
});

IndexedDB.clearObjectStore Command

Clears all entries from an object store.

securityOrigin
String Security origin.
databaseName
String Database name.
objectStoreName
String Object store name.

Callback Parameters:

Code Example:

// WebInspector Command: IndexedDB.clearObjectStore
IndexedDB.clearObjectStore(securityOrigin, databaseName, objectStoreName, function callback(res) {
	// res = {}
});

Input

Type Command

Input.TouchPoint Type

state
( touchPressed | touchReleased | touchMoved | touchStationary | touchCancelled ) State of the touch point.
x
Integer X coordinate of the event relative to the main frame's viewport.
y
Integer Y coordinate of the event relative to the main frame's viewport. 0 refers to the top of the viewport and Y increases as it proceeds towards the bottom of the viewport.
radiusX (optional)
Integer X radius of the touch area (default: 1).
radiusY (optional)
Integer Y radius of the touch area (default: 1).
rotationAngle (optional)
Number Rotation angle (default: 0.0).
force (optional)
Number Force (default: 1.0).
id (optional)
Number Identifier used to track touch sources between events, must be unique within an event.

Input.dispatchKeyEvent Command

Dispatches a key event to the page.

type
( keyDown | keyUp | rawKeyDown | char ) Type of the key event.
modifiers (optional)
Integer Bit field representing pressed modifier keys. Alt=1, Ctrl=2, Meta/Command=4, Shift=8 (default: 0).
timestamp (optional)
Number Time at which the event occurred. Measured in UTC time in seconds since January 1, 1970 (default: current time).
text (optional)
String Text as generated by processing a virtual key code with a keyboard layout. Not needed for for keyUp and rawKeyDown events (default: "")
unmodifiedText (optional)
String Text that would have been generated by the keyboard if no modifiers were pressed (except for shift). Useful for shortcut (accelerator) key handling (default: "").
keyIdentifier (optional)
String Unique key identifier (e.g., 'U+0041') (default: "").
windowsVirtualKeyCode (optional)
Integer Windows virtual key code (default: 0).
nativeVirtualKeyCode (optional)
Integer Native virtual key code (default: 0).
macCharCode (optional)
Integer Mac character code (default: 0).
autoRepeat (optional)
Boolean Whether the event was generated from auto repeat (default: false).
isKeypad (optional)
Boolean Whether the event was generated from the keypad (default: false).
isSystemKey (optional)
Boolean Whether the event was a system key event (default: false).

Code Example:

// WebInspector Command: Input.dispatchKeyEvent
Input.dispatchKeyEvent(type, modifiers, timestamp, text, unmodifiedText, keyIdentifier, windowsVirtualKeyCode, nativeVirtualKeyCode, macCharCode, autoRepeat, isKeypad, isSystemKey);

Input.dispatchMouseEvent Command

Dispatches a mouse event to the page.

type
( mousePressed | mouseReleased | mouseMoved ) Type of the mouse event.
x
Integer X coordinate of the event relative to the main frame's viewport.
y
Integer Y coordinate of the event relative to the main frame's viewport. 0 refers to the top of the viewport and Y increases as it proceeds towards the bottom of the viewport.
modifiers (optional)
Integer Bit field representing pressed modifier keys. Alt=1, Ctrl=2, Meta/Command=4, Shift=8 (default: 0).
timestamp (optional)
Number Time at which the event occurred. Measured in UTC time in seconds since January 1, 1970 (default: current time).
button (optional)
( none | left | middle | right ) Mouse button (default: "none").
clickCount (optional)
Integer Number of times the mouse button was clicked (default: 0).
deviceSpace (optional)
Boolean If true, x and y are given in dip wrt current viewport.

Code Example:

// WebInspector Command: Input.dispatchMouseEvent
Input.dispatchMouseEvent(type, x, y, modifiers, timestamp, button, clickCount, deviceSpace);

Input.dispatchTouchEvent Command

Dispatches a touch event to the page.

type
( touchStart | touchEnd | touchMove ) Type of the touch event.
touchPoints
[Input.TouchPoint] Touch points.
modifiers (optional)
Integer Bit field representing pressed modifier keys. Alt=1, Ctrl=2, Meta/Command=4, Shift=8 (default: 0).
timestamp (optional)
Number Time at which the event occurred. Measured in UTC time in seconds since January 1, 1970 (default: current time).

Code Example:

// WebInspector Command: Input.dispatchTouchEvent
Input.dispatchTouchEvent(type, touchPoints, modifiers, timestamp);

Input.dispatchGestureEvent Command

Dispatches a gesture event to the page.

type
( scrollBegin | scrollEnd | scrollUpdate | tapDown | tap | pinchBegin | pinchEnd | pinchUpdate ) Type of the gesture event.
x
Integer X coordinate relative to the screen's viewport.
y
Integer Y coordinate relative to the screen's viewport.
timestamp (optional)
Number Time at which the event occurred. Measured in UTC time in seconds since January 1, 1970 (default: current time).
deltaX (optional)
Integer Delta X where apllies.
deltaY (optional)
Integer Delta Y where apllies.
pinchScale (optional)
Number Pinch scale.

Code Example:

// WebInspector Command: Input.dispatchGestureEvent
Input.dispatchGestureEvent(type, x, y, timestamp, deltaX, deltaY, pinchScale);

Inspector

Command Event

Inspector.enable Command

Enables inspector domain notifications.

Code Example:

// WebInspector Command: Inspector.enable
Inspector.enable();

Inspector.disable Command

Disables inspector domain notifications.

Code Example:

// WebInspector Command: Inspector.disable
Inspector.disable();

Inspector.reset Command

Resets all domains.

Code Example:

// WebInspector Command: Inspector.reset
Inspector.reset();

Inspector.evaluateForTestInFrontend Event

testCallId
Integer
script
String

Code Example:

// WebInspector Event: Inspector.evaluateForTestInFrontend
function onEvaluateForTestInFrontend(res) {
	// res = {testCallId, script}
}

Inspector.inspect Event

object
Runtime.RemoteObject
hints
Object

Code Example:

// WebInspector Event: Inspector.inspect
function onInspect(res) {
	// res = {object, hints}
}

Inspector.detached Event

Fired when remote debugging connection is about to be terminated. Contains detach reason.

reason
String The reason why connection has been terminated.

Code Example:

// WebInspector Event: Inspector.detached
function onDetached(res) {
	// res = {reason}
}

Inspector.targetCrashed Event

Fired when debugging target has crashed

Code Example:

// WebInspector Event: Inspector.targetCrashed
function onTargetCrashed(res) {
	// res = {}
}

LayerTree

Type Command Event

LayerTree.LayerId Type

Unique RenderLayer identifier.

String

LayerTree.Layer Type

Information about a compositing layer.

layerId
LayerTree.LayerId The unique id for this layer.
parentLayerId (optional)
LayerTree.LayerId The id of parent (not present for root).
nodeId (optional)
DOM.NodeId The id for the node associated with this layer.
offsetX
Number Offset from parent layer, X coordinate.
offsetY
Number Offset from parent layer, X coordinate.
width
Number Layer width.
height
Number Layer height.
transform (optional)
[Number] Transformation matrix for layer, default is identity matrix
anchorX (optional)
Number Transform anchor point X, absent if no transform specified
anchorY (optional)
Number Transform anchor point Y, absent if no transform specified
anchorZ (optional)
Number Transform anchor point Z, absent if no transform specified
paintCount
Integer Indicates how many time this layer has painted.
invisible (optional)
Boolean Set if layer is not visible.

LayerTree.enable Command

Enables compositing tree inspection.

Code Example:

// WebInspector Command: LayerTree.enable
LayerTree.enable();

LayerTree.disable Command

Disables compositing tree inspection.

Code Example:

// WebInspector Command: LayerTree.disable
LayerTree.disable();

LayerTree.getLayers Command

Returns the layer tree structure of the current page.

nodeId (optional)
DOM.NodeId Root of the subtree for which we want to gather layers (return entire tree if not specified)

Callback Parameters:

layers
[LayerTree.Layer] Child layers.

Code Example:

// WebInspector Command: LayerTree.getLayers
LayerTree.getLayers(nodeId, function callback(res) {
	// res = {layers}
});

LayerTree.compositingReasons Command

Provides the reasons why the given layer was composited.

layerId
LayerTree.LayerId The id of the layer for which we want to get the reasons it was composited.

Callback Parameters:

compositingReasons
[String] A list of strings specifying reasons for the given layer to become composited.

Code Example:

// WebInspector Command: LayerTree.compositingReasons
LayerTree.compositingReasons(layerId, function callback(res) {
	// res = {compositingReasons}
});

LayerTree.layerTreeDidChange Event

Code Example:

// WebInspector Event: LayerTree.layerTreeDidChange
function onLayerTreeDidChange(res) {
	// res = {}
}

Memory

Type Command Event

Memory.MemoryBlock Type

size (optional)
Number Size of the block in bytes if available
name
String Unique name used to identify the component that allocated this block
children (optional)
[Memory.MemoryBlock]

Memory.HeapSnapshotChunk Type

strings
[String] An array of strings that were found since last update.
nodes
[Integer] An array of nodes that were found since last update.
edges
[Integer] An array of edges that were found since last update.
baseToRealNodeId
[Integer] An array of integers for nodeId remapping. Even nodeId has to be mapped to the following odd nodeId.

Memory.getDOMCounters Command

Callback Parameters:

documents
Integer
nodes
Integer
jsEventListeners
Integer

Code Example:

// WebInspector Command: Memory.getDOMCounters
Memory.getDOMCounters(function callback(res) {
	// res = {documents, nodes, jsEventListeners}
});

Memory.addNativeSnapshotChunk Event

chunk
Memory.HeapSnapshotChunk A chunk of the serialized the snapshot.

Code Example:

// WebInspector Event: Memory.addNativeSnapshotChunk
function onAddNativeSnapshotChunk(res) {
	// res = {chunk}
}

Network

Network domain allows tracking network activities of the page. It exposes information about http, file, data and other requests and responses, their headers, bodies, timing, etc.

Type Command Event

Network.LoaderId Type

Unique loader identifier.

String

Network.RequestId Type

Unique request identifier.

String

Network.Timestamp Type

Number of seconds since epoch.

Number

Network.Headers Type

Request / response headers as keys / values of JSON object.

Network.ResourceTiming Type

Timing information for the request.

requestTime
Number Timing's requestTime is a baseline in seconds, while the other numbers are ticks in milliseconds relatively to this requestTime.
proxyStart
Number Started resolving proxy.
proxyEnd
Number Finished resolving proxy.
dnsStart
Number Started DNS address resolve.
dnsEnd
Number Finished DNS address resolve.
connectStart
Number Started connecting to the remote host.
connectEnd
Number Connected to the remote host.
sslStart
Number Started SSL handshake.
sslEnd
Number Finished SSL handshake.
sendStart
Number Started sending request.
sendEnd
Number Finished sending request.
receiveHeadersEnd
Number Finished receiving response headers.

Network.Request Type

HTTP request data.

url
String Request URL.
method
String HTTP request method.
headers
Network.Headers HTTP request headers.
postData (optional)
String HTTP POST request data.

Network.Response Type

HTTP response data.

url
String Response URL. This URL can be different from CachedResource.url in case of redirect.
status
Number HTTP response status code.
statusText
String HTTP response status text.
headers
Network.Headers HTTP response headers.
headersText (optional)
String HTTP response headers text.
mimeType
String Resource mimeType as determined by the browser.
requestHeaders (optional)
Network.Headers Refined HTTP request headers that were actually transmitted over the network.
requestHeadersText (optional)
String HTTP request headers text.
connectionReused
Boolean Specifies whether physical connection was actually reused for this request.
connectionId
Number Physical connection id that was actually used for this request.
fromDiskCache (optional)
Boolean Specifies that the request was served from the disk cache.
timing (optional)
Network.ResourceTiming Timing information for the given request.

Network.WebSocketRequest Type

WebSocket request data.

headers
Network.Headers HTTP response headers.

Network.WebSocketResponse Type

WebSocket response data.

status
Number HTTP response status code.
statusText
String HTTP response status text.
headers
Network.Headers HTTP response headers.

Network.WebSocketFrame Type

WebSocket frame data.

opcode
Number WebSocket frame opcode.
mask
Boolean WebSocke frame mask.
payloadData
String WebSocke frame payload data.

Network.CachedResource Type

Information about the cached resource.

url
String Resource URL. This is the url of the original network request.
type
Page.ResourceType Type of this resource.
response (optional)
Network.Response Cached response data.
bodySize
Number Cached response body size.

Network.Initiator Type

Information about the request initiator.

type
( parser | script | other ) Type of this initiator.
stackTrace (optional)
Console.StackTrace Initiator JavaScript stack trace, set for Script only.
url (optional)
String Initiator URL, set for Parser type only.
lineNumber (optional)
Number Initiator line number, set for Parser type only.

Network.enable Command

Enables network tracking, network events will now be delivered to the client.

Code Example:

// WebInspector Command: Network.enable
Network.enable();

Network.disable Command

Disables network tracking, prevents network events from being sent to the client.

Code Example:

// WebInspector Command: Network.disable
Network.disable();

Network.setUserAgentOverride Command

Allows overriding user agent with the given string.

userAgent
String User agent to use.

Code Example:

// WebInspector Command: Network.setUserAgentOverride
Network.setUserAgentOverride(userAgent);

Network.setExtraHTTPHeaders Command

Specifies whether to always send extra HTTP headers with the requests from this page.

headers
Network.Headers Map with extra HTTP headers.

Code Example:

// WebInspector Command: Network.setExtraHTTPHeaders
Network.setExtraHTTPHeaders(headers);

Network.getResponseBody Command

Returns content served for the given request.

requestId
Network.RequestId Identifier of the network request to get content for.

Callback Parameters:

body
String Response body.
base64Encoded
Boolean True, if content was sent as base64.

Code Example:

// WebInspector Command: Network.getResponseBody
Network.getResponseBody(requestId, function callback(res) {
	// res = {body, base64Encoded}
});

Network.replayXHR Command

This method sends a new XMLHttpRequest which is identical to the original one. The following parameters should be identical: method, url, async, request body, extra headers, withCredentials attribute, user, password.

requestId
Network.RequestId Identifier of XHR to replay.

Code Example:

// WebInspector Command: Network.replayXHR
Network.replayXHR(requestId);

Network.canClearBrowserCache Command

Tells whether clearing browser cache is supported.

Callback Parameters:

result
Boolean True if browser cache can be cleared.

Code Example:

// WebInspector Command: Network.canClearBrowserCache
Network.canClearBrowserCache(function callback(res) {
	// res = {result}
});

Network.clearBrowserCache Command

Clears browser cache.

Code Example:

// WebInspector Command: Network.clearBrowserCache
Network.clearBrowserCache();

Network.canClearBrowserCookies Command

Tells whether clearing browser cookies is supported.

Callback Parameters:

result
Boolean True if browser cookies can be cleared.

Code Example:

// WebInspector Command: Network.canClearBrowserCookies
Network.canClearBrowserCookies(function callback(res) {
	// res = {result}
});

Network.clearBrowserCookies Command

Clears browser cookies.

Code Example:

// WebInspector Command: Network.clearBrowserCookies
Network.clearBrowserCookies();

Network.setCacheDisabled Command

Toggles ignoring cache for each request. If true, cache will not be used.

cacheDisabled
Boolean Cache disabled state.

Code Example:

// WebInspector Command: Network.setCacheDisabled
Network.setCacheDisabled(cacheDisabled);

Network.loadResourceForFrontend Command

Loads a resource in the context of a frame on the inspected page without cross origin checks.

frameId
Page.FrameId Frame to load the resource from.
url
String URL of the resource to load.
requestHeaders (optional)
Network.Headers Request headers.

Callback Parameters:

statusCode
Number HTTP status code.
responseHeaders
Network.Headers Response headers.
content
String Resource content.

Code Example:

// WebInspector Command: Network.loadResourceForFrontend
Network.loadResourceForFrontend(frameId, url, requestHeaders, function callback(res) {
	// res = {statusCode, responseHeaders, content}
});

Network.requestWillBeSent Event

Fired when page is about to send HTTP request.

requestId
Network.RequestId Request identifier.
frameId
Page.FrameId Frame identifier.
loaderId
Network.LoaderId Loader identifier.
documentURL
String URL of the document this request is loaded for.
request
Network.Request Request data.
timestamp
Network.Timestamp Timestamp.
initiator
Network.Initiator Request initiator.
redirectResponse (optional)
Network.Response Redirect response data.

Code Example:

// WebInspector Event: Network.requestWillBeSent
function onRequestWillBeSent(res) {
	// res = {requestId, frameId, loaderId, documentURL, request, timestamp, initiator, redirectResponse}
}

Network.requestServedFromCache Event

Fired if request ended up loading from cache.

requestId
Network.RequestId Request identifier.

Code Example:

// WebInspector Event: Network.requestServedFromCache
function onRequestServedFromCache(res) {
	// res = {requestId}
}

Network.responseReceived Event

Fired when HTTP response is available.

requestId
Network.RequestId Request identifier.
frameId
Page.FrameId Frame identifier.
loaderId
Network.LoaderId Loader identifier.
timestamp
Network.Timestamp Timestamp.
type
Page.ResourceType Resource type.
response
Network.Response Response data.

Code Example:

// WebInspector Event: Network.responseReceived
function onResponseReceived(res) {
	// res = {requestId, frameId, loaderId, timestamp, type, response}
}

Network.dataReceived Event

Fired when data chunk was received over the network.

requestId
Network.RequestId Request identifier.
timestamp
Network.Timestamp Timestamp.
dataLength
Integer Data chunk length.
encodedDataLength
Integer Actual bytes received (might be less than dataLength for compressed encodings).

Code Example:

// WebInspector Event: Network.dataReceived
function onDataReceived(res) {
	// res = {requestId, timestamp, dataLength, encodedDataLength}
}

Network.loadingFinished Event

Fired when HTTP request has finished loading.

requestId
Network.RequestId Request identifier.
timestamp
Network.Timestamp Timestamp.

Code Example:

// WebInspector Event: Network.loadingFinished
function onLoadingFinished(res) {
	// res = {requestId, timestamp}
}

Network.loadingFailed Event

Fired when HTTP request has failed to load.

requestId
Network.RequestId Request identifier.
timestamp
Network.Timestamp Timestamp.
errorText
String User friendly error message.
canceled (optional)
Boolean True if loading was canceled.

Code Example:

// WebInspector Event: Network.loadingFailed
function onLoadingFailed(res) {
	// res = {requestId, timestamp, errorText, canceled}
}

Network.webSocketWillSendHandshakeRequest Event

Fired when WebSocket is about to initiate handshake.

requestId
Network.RequestId Request identifier.
timestamp
Network.Timestamp Timestamp.
request
Network.WebSocketRequest WebSocket request data.

Code Example:

// WebInspector Event: Network.webSocketWillSendHandshakeRequest
function onWebSocketWillSendHandshakeRequest(res) {
	// res = {requestId, timestamp, request}
}

Network.webSocketHandshakeResponseReceived Event

Fired when WebSocket handshake response becomes available.

requestId
Network.RequestId Request identifier.
timestamp
Network.Timestamp Timestamp.
response
Network.WebSocketResponse WebSocket response data.

Code Example:

// WebInspector Event: Network.webSocketHandshakeResponseReceived
function onWebSocketHandshakeResponseReceived(res) {
	// res = {requestId, timestamp, response}
}

Network.webSocketCreated Event

Fired upon WebSocket creation.

requestId
Network.RequestId Request identifier.
url
String WebSocket request URL.

Code Example:

// WebInspector Event: Network.webSocketCreated
function onWebSocketCreated(res) {
	// res = {requestId, url}
}

Network.webSocketClosed Event

Fired when WebSocket is closed.

requestId
Network.RequestId Request identifier.
timestamp
Network.Timestamp Timestamp.

Code Example:

// WebInspector Event: Network.webSocketClosed
function onWebSocketClosed(res) {
	// res = {requestId, timestamp}
}

Network.webSocketFrameReceived Event

Fired when WebSocket frame is received.

requestId
Network.RequestId Request identifier.
timestamp
Network.Timestamp Timestamp.
response
Network.WebSocketFrame WebSocket response data.

Code Example:

// WebInspector Event: Network.webSocketFrameReceived
function onWebSocketFrameReceived(res) {
	// res = {requestId, timestamp, response}
}

Network.webSocketFrameError Event

Fired when WebSocket frame error occurs.

requestId
Network.RequestId Request identifier.
timestamp
Network.Timestamp Timestamp.
errorMessage
String WebSocket frame error message.

Code Example:

// WebInspector Event: Network.webSocketFrameError
function onWebSocketFrameError(res) {
	// res = {requestId, timestamp, errorMessage}
}

Network.webSocketFrameSent Event

Fired when WebSocket frame is sent.

requestId
Network.RequestId Request identifier.
timestamp
Network.Timestamp Timestamp.
response
Network.WebSocketFrame WebSocket response data.

Code Example:

// WebInspector Event: Network.webSocketFrameSent
function onWebSocketFrameSent(res) {
	// res = {requestId, timestamp, response}
}

Page

Actions and events related to the inspected page belong to the page domain.

Type Command Event

Page.ResourceType Type

Resource type as it was perceived by the rendering engine.

( Document | Stylesheet | Image | Font | Script | XHR | WebSocket | Other )

Page.FrameId Type

Unique frame identifier.

String

Page.Frame Type

Information about the Frame on the page.

id
String Frame unique identifier.
parentId (optional)
String Parent frame identifier.
loaderId
Network.LoaderId Identifier of the loader associated with this frame.
name (optional)
String Frame's name as specified in the tag.
url
String Frame document's URL.
securityOrigin
String Frame document's security origin.
mimeType
String Frame document's mimeType as determined by the browser.

Page.FrameResourceTree Type

Information about the Frame hierarchy along with their cached resources.

frame
Page.Frame Frame information for this tree item.
childFrames (optional)
[Page.FrameResourceTree] Child frames.
resources
[Object] Information about frame resources.

Page.SearchMatch Type

Search match for resource.

lineNumber
Number Line number in resource content.
lineContent
String Line with match content.

Page.SearchResult Type

Search result for resource.

url
String Resource URL.
frameId
Page.FrameId Resource frame id.
matchesCount
Number Number of matches in the resource content.

Page.ScriptIdentifier Type

Unique script identifier.

String

Page.enable Command

Enables page domain notifications.

Code Example:

// WebInspector Command: Page.enable
Page.enable();

Page.disable Command

Disables page domain notifications.

Code Example:

// WebInspector Command: Page.disable
Page.disable();

Page.addScriptToEvaluateOnLoad Command

scriptSource
String

Callback Parameters:

identifier
Page.ScriptIdentifier Identifier of the added script.

Code Example:

// WebInspector Command: Page.addScriptToEvaluateOnLoad
Page.addScriptToEvaluateOnLoad(scriptSource, function callback(res) {
	// res = {identifier}
});

Page.removeScriptToEvaluateOnLoad Command

identifier
Page.ScriptIdentifier

Code Example:

// WebInspector Command: Page.removeScriptToEvaluateOnLoad
Page.removeScriptToEvaluateOnLoad(identifier);

Page.reload Command

Reloads given page optionally ignoring the cache.

ignoreCache (optional)
Boolean If true, browser cache is ignored (as if the user pressed Shift+refresh).
scriptToEvaluateOnLoad (optional)
String If set, the script will be injected into all frames of the inspected page after reload.
scriptPreprocessor (optional)
String Script body that should evaluate to function that will preprocess all the scripts before their compilation.

Code Example:

// WebInspector Command: Page.reload
Page.reload(ignoreCache, scriptToEvaluateOnLoad, scriptPreprocessor);

Page.getNavigationHistory Command

Returns navigation history for the current page.

Callback Parameters:

currentIndex
Integer Index of the current navigation history entry.
entries
[Page.NavigationEntry] Array of navigation history entries.

Code Example:

// WebInspector Command: Page.getNavigationHistory
Page.getNavigationHistory(function callback(res) {
	// res = {currentIndex, entries}
});

Page.getCookies Command

Returns all browser cookies. Depending on the backend support, will either return detailed cookie information in the cookie field or string cookie representation using cookieString.

Callback Parameters:

cookies
[Page.Cookie] Array of cookie objects.
cookiesString
String document.cookie string representation of the cookies.

Code Example:

// WebInspector Command: Page.getCookies
Page.getCookies(function callback(res) {
	// res = {cookies, cookiesString}
});

Page.deleteCookie Command

Deletes browser cookie with given name, domain and path.

cookieName
String Name of the cookie to remove.
url
String URL to match cooke domain and path.

Code Example:

// WebInspector Command: Page.deleteCookie
Page.deleteCookie(cookieName, url);

Page.getResourceTree Command

Returns present frame / resource tree structure.

Callback Parameters:

frameTree
Page.FrameResourceTree Present frame / resource tree structure.

Code Example:

// WebInspector Command: Page.getResourceTree
Page.getResourceTree(function callback(res) {
	// res = {frameTree}
});

Page.getResourceContent Command

Returns content of the given resource.

frameId
Page.FrameId Frame id to get resource for.
url
String URL of the resource to get content for.

Callback Parameters:

content
String Resource content.
base64Encoded
Boolean True, if content was served as base64.

Code Example:

// WebInspector Command: Page.getResourceContent
Page.getResourceContent(frameId, url, function callback(res) {
	// res = {content, base64Encoded}
});

Page.searchInResource Command

Searches for given string in resource content.

frameId
Page.FrameId Frame id for resource to search in.
url
String URL of the resource to search in.
query
String String to search for.
caseSensitive (optional)
Boolean If true, search is case sensitive.
isRegex (optional)
Boolean If true, treats string parameter as regex.

Callback Parameters:

result
[Page.SearchMatch] List of search matches.

Code Example:

// WebInspector Command: Page.searchInResource
Page.searchInResource(frameId, url, query, caseSensitive, isRegex, function callback(res) {
	// res = {result}
});

Page.searchInResources Command

Searches for given string in frame / resource tree structure.

text
String String to search for.
caseSensitive (optional)
Boolean If true, search is case sensitive.
isRegex (optional)
Boolean If true, treats string parameter as regex.

Callback Parameters:

result
[Page.SearchResult] List of search results.

Code Example:

// WebInspector Command: Page.searchInResources
Page.searchInResources(text, caseSensitive, isRegex, function callback(res) {
	// res = {result}
});

Page.setDocumentContent Command

Sets given markup as the document's HTML.

frameId
Page.FrameId Frame id to set HTML for.
html
String HTML content to set.

Code Example:

// WebInspector Command: Page.setDocumentContent
Page.setDocumentContent(frameId, html);

Page.setDeviceMetricsOverride Command

Overrides the values of device screen dimensions (window.screen.width, window.screen.height, window.innerWidth, window.innerHeight, and "device-width"/"device-height"-related CSS media query results) and the font scale factor.

width
Integer Overriding width value in pixels (minimum 0, maximum 10000000). 0 disables the override.
height
Integer Overriding height value in pixels (minimum 0, maximum 10000000). 0 disables the override.
fontScaleFactor
Number Overriding font scale factor value (must be positive). 1 disables the override.
fitWindow
Boolean Whether a view that exceeds the available browser window area should be scaled down to fit.

Code Example:

// WebInspector Command: Page.setDeviceMetricsOverride
Page.setDeviceMetricsOverride(width, height, fontScaleFactor, fitWindow);

Page.setShowPaintRects Command

Requests that backend shows paint rectangles

result
Boolean True for showing paint rectangles

Code Example:

// WebInspector Command: Page.setShowPaintRects
Page.setShowPaintRects(result);

Page.setShowDebugBorders Command

Requests that backend shows debug borders on layers

show
Boolean True for showing debug borders

Code Example:

// WebInspector Command: Page.setShowDebugBorders
Page.setShowDebugBorders(show);

Page.setShowFPSCounter Command

Requests that backend shows the FPS counter

show
Boolean True for showing the FPS counter

Code Example:

// WebInspector Command: Page.setShowFPSCounter
Page.setShowFPSCounter(show);

Page.setContinuousPaintingEnabled Command

Requests that backend enables continuous painting

enabled
Boolean True for enabling cointinuous painting

Code Example:

// WebInspector Command: Page.setContinuousPaintingEnabled
Page.setContinuousPaintingEnabled(enabled);

Page.setShowScrollBottleneckRects Command

Requests that backend shows scroll bottleneck rects

show
Boolean True for showing scroll bottleneck rects

Code Example:

// WebInspector Command: Page.setShowScrollBottleneckRects
Page.setShowScrollBottleneckRects(show);

Page.getScriptExecutionStatus Command

Determines if scripts can be executed in the page.

Callback Parameters:

result
( allowed | disabled | forbidden ) Script execution status: "allowed" if scripts can be executed, "disabled" if script execution has been disabled through page settings, "forbidden" if script execution for the given page is not possible for other reasons.

Code Example:

// WebInspector Command: Page.getScriptExecutionStatus
Page.getScriptExecutionStatus(function callback(res) {
	// res = {result}
});

Page.setScriptExecutionDisabled Command

Switches script execution in the page.

value
Boolean Whether script execution should be disabled in the page.

Code Example:

// WebInspector Command: Page.setScriptExecutionDisabled
Page.setScriptExecutionDisabled(value);

Page.setGeolocationOverride Command

Overrides the Geolocation Position or Error.

latitude (optional)
Number Mock longitude
longitude (optional)
Number Mock latitude
accuracy (optional)
Number Mock accuracy

Code Example:

// WebInspector Command: Page.setGeolocationOverride
Page.setGeolocationOverride(latitude, longitude, accuracy);

Page.clearGeolocationOverride Command

Clears the overriden Geolocation Position and Error.

Code Example:

// WebInspector Command: Page.clearGeolocationOverride
Page.clearGeolocationOverride();

Page.setDeviceOrientationOverride Command

Overrides the Device Orientation.

alpha
Number Mock alpha
beta
Number Mock beta
gamma
Number Mock gamma

Code Example:

// WebInspector Command: Page.setDeviceOrientationOverride
Page.setDeviceOrientationOverride(alpha, beta, gamma);

Page.clearDeviceOrientationOverride Command

Clears the overridden Device Orientation.

Code Example:

// WebInspector Command: Page.clearDeviceOrientationOverride
Page.clearDeviceOrientationOverride();

Page.setTouchEmulationEnabled Command

Toggles mouse event-based touch event emulation.

enabled
Boolean Whether the touch event emulation should be enabled.

Code Example:

// WebInspector Command: Page.setTouchEmulationEnabled
Page.setTouchEmulationEnabled(enabled);

Page.setEmulatedMedia Command

Emulates the given media for CSS media queries.

media
String Media type to emulate. Empty string disables the override.

Code Example:

// WebInspector Command: Page.setEmulatedMedia
Page.setEmulatedMedia(media);

Page.captureScreenshot Command

Capture page screenshot.

format (optional)
( jpeg | png ) Image compression format.
quality (optional)
Integer Compression quality from range [0..100].
maxWidth (optional)
Integer Maximum screenshot width.
maxHeight (optional)
Integer Maximum screenshot height.

Callback Parameters:

data
String Base64-encoded image data (PNG).
deviceScaleFactor
Number Device scale factor.
pageScaleFactor
Number Page scale factor.
viewport
DOM.Rect Viewport in CSS pixels.

Code Example:

// WebInspector Command: Page.captureScreenshot
Page.captureScreenshot(format, quality, maxWidth, maxHeight, function callback(res) {
	// res = {data, deviceScaleFactor, pageScaleFactor, viewport}
});

Page.startScreencast Command

Starts sending each frame using the screencastFrame event.

format (optional)
( jpeg | png ) Image compression format.
quality (optional)
Integer Compression quality from range [0..100].
maxWidth (optional)
Integer Maximum screenshot width.
maxHeight (optional)
Integer Maximum screenshot height.

Code Example:

// WebInspector Command: Page.startScreencast
Page.startScreencast(format, quality, maxWidth, maxHeight);

Page.stopScreencast Command

Stops sending each frame in the screencastFrame.

Code Example:

// WebInspector Command: Page.stopScreencast
Page.stopScreencast();

Page.handleJavaScriptDialog Command

Accepts or dismisses a JavaScript initiated dialog (alert, confirm, prompt, or onbeforeunload).

accept
Boolean Whether to accept or dismiss the dialog.
promptText (optional)
String The text to enter into the dialog prompt before accepting. Used only if this is a prompt dialog.

Code Example:

// WebInspector Command: Page.handleJavaScriptDialog
Page.handleJavaScriptDialog(accept, promptText);

Page.setShowViewportSizeOnResize Command

Paints viewport size upon main frame resize.

show
Boolean Whether to paint size or not.
showGrid (optional)
Boolean Whether to paint grid as well.

Code Example:

// WebInspector Command: Page.setShowViewportSizeOnResize
Page.setShowViewportSizeOnResize(show, showGrid);

Page.setForceCompositingMode Command

Force accelerated compositing mode for inspected page.

force
Boolean Whether to force accelerated compositing or not.

Code Example:

// WebInspector Command: Page.setForceCompositingMode
Page.setForceCompositingMode(force);

Page.domContentEventFired Event

timestamp
Number

Code Example:

// WebInspector Event: Page.domContentEventFired
function onDomContentEventFired(res) {
	// res = {timestamp}
}

Page.loadEventFired Event

timestamp
Number

Code Example:

// WebInspector Event: Page.loadEventFired
function onLoadEventFired(res) {
	// res = {timestamp}
}

Page.frameAttached Event

Fired when frame has been attached to its parent.

frameId
Page.FrameId Id of the frame that has been attached.

Code Example:

// WebInspector Event: Page.frameAttached
function onFrameAttached(res) {
	// res = {frameId}
}

Page.frameNavigated Event

Fired once navigation of the frame has completed. Frame is now associated with the new loader.

frame
Page.Frame Frame object.

Code Example:

// WebInspector Event: Page.frameNavigated
function onFrameNavigated(res) {
	// res = {frame}
}

Page.frameDetached Event

Fired when frame has been detached from its parent.

frameId
Page.FrameId Id of the frame that has been detached.

Code Example:

// WebInspector Event: Page.frameDetached
function onFrameDetached(res) {
	// res = {frameId}
}

Page.frameStartedLoading Event

Fired when frame has started loading.

frameId
Page.FrameId Id of the frame that has started loading.

Code Example:

// WebInspector Event: Page.frameStartedLoading
function onFrameStartedLoading(res) {
	// res = {frameId}
}

Page.frameStoppedLoading Event

Fired when frame has stopped loading.

frameId
Page.FrameId Id of the frame that has stopped loading.

Code Example:

// WebInspector Event: Page.frameStoppedLoading
function onFrameStoppedLoading(res) {
	// res = {frameId}
}

Page.frameScheduledNavigation Event

Fired when frame schedules a potential navigation.

frameId
Page.FrameId Id of the frame that has scheduled a navigation.
delay
Number Delay (in seconds) until the navigation is scheduled to begin. The navigation is not guaranteed to start.

Code Example:

// WebInspector Event: Page.frameScheduledNavigation
function onFrameScheduledNavigation(res) {
	// res = {frameId, delay}
}

Page.frameClearedScheduledNavigation Event

Fired when frame no longer has a scheduled navigation.

frameId
Page.FrameId Id of the frame that has cleared its scheduled navigation.

Code Example:

// WebInspector Event: Page.frameClearedScheduledNavigation
function onFrameClearedScheduledNavigation(res) {
	// res = {frameId}
}

Page.javascriptDialogOpening Event

Fired when a JavaScript initiated dialog (alert, confirm, prompt, or onbeforeunload) is about to open.

message
String Message that will be displayed by the dialog.

Code Example:

// WebInspector Event: Page.javascriptDialogOpening
function onJavascriptDialogOpening(res) {
	// res = {message}
}

Page.javascriptDialogClosed Event

Fired when a JavaScript initiated dialog (alert, confirm, prompt, or onbeforeunload) has been closed.

Code Example:

// WebInspector Event: Page.javascriptDialogClosed
function onJavascriptDialogClosed(res) {
	// res = {}
}

Page.scriptsEnabled Event

Fired when the JavaScript is enabled/disabled on the page

isEnabled
Boolean Whether script execution is enabled or disabled on the page.

Code Example:

// WebInspector Event: Page.scriptsEnabled
function onScriptsEnabled(res) {
	// res = {isEnabled}
}

Page.screencastFrame Event

Compressed image data requested by the startScreencast.

data
String Base64-encoded compressed image.
deviceScaleFactor (optional)
Number Device scale factor.
pageScaleFactor (optional)
Number Page scale factor.
viewport (optional)
DOM.Rect Viewport in CSS pixels.
offsetTop (optional)
Number Top offset in DIP.
offsetBottom (optional)
Number Bottom offset in DIP.

Code Example:

// WebInspector Event: Page.screencastFrame
function onScreencastFrame(res) {
	// res = {data, deviceScaleFactor, pageScaleFactor, viewport, offsetTop, offsetBottom}
}

Page.screencastVisibilityChanged Event

Fired when the page with currently enabled screencast was shown or hidden .

visible
Boolean True if the page is visible.

Code Example:

// WebInspector Event: Page.screencastVisibilityChanged
function onScreencastVisibilityChanged(res) {
	// res = {visible}
}

Profiler

Type Command Event

Profiler.ProfileHeader Type

Profile header.

title
String Profile title.
uid
Integer Unique identifier of the profile.

Profiler.CPUProfileNode Type

CPU Profile node. Holds callsite information, execution statistics and child nodes.

functionName
String Function name.
scriptId
Debugger.ScriptId Script identifier.
url
String URL.
lineNumber
Integer Line number.
hitCount
Integer Number of samples where this node was on top of the call stack.
callUID
Number Call UID.
children
[Profiler.CPUProfileNode] Child nodes.
deoptReason
String The reason of being not optimized. The function may be deoptimized or marked as don't optimize.
id (optional)
Integer Unique id of the node.

Profiler.CPUProfile Type

Profile.

head
Profiler.CPUProfileNode
startTime
Number Profiling start time in seconds.
endTime
Number Profiling end time in seconds.
samples (optional)
[Integer] Ids of samples top nodes.

Profiler.HeapSnapshotObjectId Type

Heap snashot object id.

String

Profiler.enable Command

Code Example:

// WebInspector Command: Profiler.enable
Profiler.enable();

Profiler.disable Command

Code Example:

// WebInspector Command: Profiler.disable
Profiler.disable();

Profiler.setSamplingInterval Command

Changes CPU profiler sampling interval. Must be called before CPU profiles recording started.

interval
Integer New sampling interval in microseconds.

Code Example:

// WebInspector Command: Profiler.setSamplingInterval
Profiler.setSamplingInterval(interval);

Profiler.start Command

Code Example:

// WebInspector Command: Profiler.start
Profiler.start();

Profiler.stop Command

Callback Parameters:

header
Profiler.ProfileHeader The header of the recorded profile.

Code Example:

// WebInspector Command: Profiler.stop
Profiler.stop(function callback(res) {
	// res = {header}
});

Profiler.getProfileHeaders Command

Callback Parameters:

headers
[Profiler.ProfileHeader]

Code Example:

// WebInspector Command: Profiler.getProfileHeaders
Profiler.getProfileHeaders(function callback(res) {
	// res = {headers}
});

Profiler.getCPUProfile Command

uid
Integer

Callback Parameters:

profile
Profiler.CPUProfile

Code Example:

// WebInspector Command: Profiler.getCPUProfile
Profiler.getCPUProfile(uid, function callback(res) {
	// res = {profile}
});

Profiler.removeProfile Command

type
String
uid
Integer

Code Example:

// WebInspector Command: Profiler.removeProfile
Profiler.removeProfile(type, uid);

Profiler.clearProfiles Command

Code Example:

// WebInspector Command: Profiler.clearProfiles
Profiler.clearProfiles();

Profiler.addProfileHeader Event

header
Profiler.ProfileHeader

Code Example:

// WebInspector Event: Profiler.addProfileHeader
function onAddProfileHeader(res) {
	// res = {header}
}

Profiler.setRecordingProfile Event

isProfiling
Boolean

Code Example:

// WebInspector Event: Profiler.setRecordingProfile
function onSetRecordingProfile(res) {
	// res = {isProfiling}
}

Profiler.resetProfiles Event

Code Example:

// WebInspector Event: Profiler.resetProfiles
function onResetProfiles(res) {
	// res = {}
}

Runtime

Runtime domain exposes JavaScript runtime by means of remote evaluation and mirror objects. Evaluation results are returned as mirror object that expose object type, string representation and unique identifier that can be used for further object reference. Original objects are maintained in memory unless they are either explicitly released or are released along with the other objects in their object group.

Type Command
  • Runtime.evaluate: Evaluates expression on global object.
  • Runtime.callFunctionOn: Calls function with given declaration on the given object. Object group of the result is inherited from the target object.
  • Runtime.getProperties: Returns properties of a given object. Object group of the result is inherited from the target object.
  • Runtime.releaseObject: Releases remote object with given id.
  • Runtime.releaseObjectGroup: Releases all remote objects that belong to a given group.
  • Runtime.run: Tells inspected instance(worker or page) that it can run in case it was started paused.
  • Runtime.enable: Enables reporting of execution contexts creation by means of executionContextCreated event. When the reporting gets enabled the event will be sent immediately for each existing execution context.
  • Runtime.disable: Disables reporting of execution contexts creation.
Event

Runtime.RemoteObjectId Type

Unique object identifier.

String

Runtime.RemoteObject Type

Mirror object referencing original JavaScript object.

type
( object | function | undefined | string | number | boolean ) Object type.
subtype (optional)
( array | null | node | regexp | date ) Object subtype hint. Specified for object type values only.
className (optional)
String Object class (constructor) name. Specified for object type values only.
value (optional)
Any Remote object value (in case of primitive values or JSON values if it was requested).
description (optional)
String String representation of the object.
objectId (optional)
Runtime.RemoteObjectId Unique object identifier (for non-primitive values).
preview (optional)
Runtime.ObjectPreview Preview containing abbreviated property values.

Runtime.ObjectPreview Type

Object containing abbreviated remote object value.

lossless
Boolean Determines whether preview is lossless (contains all information of the original object).
overflow
Boolean True iff some of the properties of the original did not fit.
properties
[Runtime.PropertyPreview] List of the properties.

Runtime.PropertyPreview Type

name
String Property name.
type
( object | function | undefined | string | number | boolean ) Object type.
value (optional)
String User-friendly property value string.
valuePreview (optional)
Runtime.ObjectPreview Nested value preview.
subtype (optional)
( array | null | node | regexp | date ) Object subtype hint. Specified for object type values only.

Runtime.PropertyDescriptor Type

Object property descriptor.

name
String Property name.
value (optional)
Runtime.RemoteObject The value associated with the property.
writable (optional)
Boolean True if the value associated with the property may be changed (data descriptors only).
get (optional)
Runtime.RemoteObject A function which serves as a getter for the property, or undefined if there is no getter (accessor descriptors only).
set (optional)
Runtime.RemoteObject A function which serves as a setter for the property, or undefined if there is no setter (accessor descriptors only).
configurable
Boolean True if the type of this property descriptor may be changed and if the property may be deleted from the corresponding object.
enumerable
Boolean True if this property shows up during enumeration of the properties on the corresponding object.
wasThrown (optional)
Boolean True if the result was thrown during the evaluation.
isOwn (optional)
Boolean True if the property is owned for the object.

Runtime.InternalPropertyDescriptor Type

Object internal property descriptor. This property isn't normally visible in JavaScript code.

name
String Conventional property name.
value (optional)
Runtime.RemoteObject The value associated with the property.

Runtime.CallArgument Type

Represents function call argument. Either remote object id objectId or primitive value or neither of (for undefined) them should be specified.

value (optional)
Any Primitive value.
objectId (optional)
Runtime.RemoteObjectId Remote object handle.

Runtime.ExecutionContextId Type

Id of an execution context.

Integer

Runtime.ExecutionContextDescription Type

Description of an isolated world.

id
Runtime.ExecutionContextId Unique id of the execution context. It can be used to specify in which execution context script evaluation should be performed.
isPageContext
Boolean True if this is a context where inspected web page scripts run. False if it is a content script isolated context.
name
String Human readable name describing given context.
frameId
Page.FrameId Id of the owning frame.

Runtime.evaluate Command

Evaluates expression on global object.

expression
String Expression to evaluate.
objectGroup (optional)
String Symbolic group name that can be used to release multiple objects.
includeCommandLineAPI (optional)
Boolean Determines whether Command Line API should be available during the evaluation.
doNotPauseOnExceptionsAndMuteConsole (optional)
Boolean Specifies whether evaluation should stop on exceptions and mute console. Overrides setPauseOnException state.
contextId (optional)
Runtime.ExecutionContextId Specifies in which isolated context to perform evaluation. Each content script lives in an isolated context and this parameter may be used to specify one of those contexts. If the parameter is omitted or 0 the evaluation will be performed in the context of the inspected page.
returnByValue (optional)
Boolean Whether the result is expected to be a JSON object that should be sent by value.
generatePreview (optional)
Boolean Whether preview should be generated for the result.

Callback Parameters:

result
Runtime.RemoteObject Evaluation result.
wasThrown (optional)
Boolean True if the result was thrown during the evaluation.

Code Example:

// WebInspector Command: Runtime.evaluate
Runtime.evaluate(expression, objectGroup, includeCommandLineAPI, doNotPauseOnExceptionsAndMuteConsole, contextId, returnByValue, generatePreview, function callback(res) {
	// res = {result, wasThrown}
});

Runtime.callFunctionOn Command

Calls function with given declaration on the given object. Object group of the result is inherited from the target object.

objectId
Runtime.RemoteObjectId Identifier of the object to call function on.
functionDeclaration
String Declaration of the function to call.
arguments (optional)
[Runtime.CallArgument] Call arguments. All call arguments must belong to the same JavaScript world as the target object.
doNotPauseOnExceptionsAndMuteConsole (optional)
Boolean Specifies whether function call should stop on exceptions and mute console. Overrides setPauseOnException state.
returnByValue (optional)
Boolean Whether the result is expected to be a JSON object which should be sent by value.
generatePreview (optional)
Boolean Whether preview should be generated for the result.

Callback Parameters:

result
Runtime.RemoteObject Call result.
wasThrown (optional)
Boolean True if the result was thrown during the evaluation.

Code Example:

// WebInspector Command: Runtime.callFunctionOn
Runtime.callFunctionOn(objectId, functionDeclaration, arguments, doNotPauseOnExceptionsAndMuteConsole, returnByValue, generatePreview, function callback(res) {
	// res = {result, wasThrown}
});

Runtime.getProperties Command

Returns properties of a given object. Object group of the result is inherited from the target object.

objectId
Runtime.RemoteObjectId Identifier of the object to return properties for.
ownProperties (optional)
Boolean If true, returns properties belonging only to the element itself, not to its prototype chain.
accessorPropertiesOnly (optional)
Boolean If true, returns accessor properties (with getter/setter) only; internal properties are not returned either.

Callback Parameters:

result
[Runtime.PropertyDescriptor] Object properties.
internalProperties (optional)
[Runtime.InternalPropertyDescriptor] Internal object properties (only of the element itself).

Code Example:

// WebInspector Command: Runtime.getProperties
Runtime.getProperties(objectId, ownProperties, accessorPropertiesOnly, function callback(res) {
	// res = {result, internalProperties}
});

Runtime.releaseObject Command

Releases remote object with given id.

objectId
Runtime.RemoteObjectId Identifier of the object to release.

Code Example:

// WebInspector Command: Runtime.releaseObject
Runtime.releaseObject(objectId);

Runtime.releaseObjectGroup Command

Releases all remote objects that belong to a given group.

objectGroup
String Symbolic object group name.

Code Example:

// WebInspector Command: Runtime.releaseObjectGroup
Runtime.releaseObjectGroup(objectGroup);

Runtime.run Command

Tells inspected instance(worker or page) that it can run in case it was started paused.

Code Example:

// WebInspector Command: Runtime.run
Runtime.run();

Runtime.enable Command

Enables reporting of execution contexts creation by means of executionContextCreated event. When the reporting gets enabled the event will be sent immediately for each existing execution context.

Code Example:

// WebInspector Command: Runtime.enable
Runtime.enable();

Runtime.disable Command

Disables reporting of execution contexts creation.

Code Example:

// WebInspector Command: Runtime.disable
Runtime.disable();

Runtime.executionContextCreated Event

Issued when new execution context is created.

context
Runtime.ExecutionContextDescription A newly created execution contex.

Code Example:

// WebInspector Event: Runtime.executionContextCreated
function onExecutionContextCreated(res) {
	// res = {context}
}

Timeline

Timeline provides its clients with instrumentation records that are generated during the page runtime. Timeline instrumentation can be started and stopped using corresponding commands. While timeline is started, it is generating timeline event records.

Type Command Event

Timeline.DOMCounters Type

Current values of DOM counters.

documents
Integer
nodes
Integer
jsEventListeners
Integer

Timeline.TimelineEvent Type

Timeline record contains information about the recorded activity.

type
String Event type.
thread (optional)
String If present, identifies the thread that produced the event.
data
Object Event data.
children (optional)
[Timeline.TimelineEvent] Nested records.
counters (optional)
Timeline.DOMCounters Current values of DOM counters.
usedHeapSize (optional)
Integer Current size of JS heap.
nativeHeapStatistics (optional)
Object Native heap statistics.

Timeline.enable Command

Enables timeline. After this call, timeline can be started from within the page (for example upon console.timeline).

Code Example:

// WebInspector Command: Timeline.enable
Timeline.enable();

Timeline.disable Command

Disables timeline.

Code Example:

// WebInspector Command: Timeline.disable
Timeline.disable();

Timeline.start Command

Starts capturing instrumentation events.

maxCallStackDepth (optional)
Integer Samples JavaScript stack traces up to maxCallStackDepth, defaults to 5.
bufferEvents (optional)
Boolean Whether instrumentation events should be buffered and returned upon stop call.
includeDomCounters (optional)
Boolean Whether DOM counters data should be included into timeline events.
includeNativeMemoryStatistics (optional)
Boolean Whether native memory usage statistics should be reported as part of timeline events.

Code Example:

// WebInspector Command: Timeline.start
Timeline.start(maxCallStackDepth, bufferEvents, includeDomCounters, includeNativeMemoryStatistics);

Timeline.stop Command

Stops capturing instrumentation events.

Callback Parameters:

events (optional)
[Timeline.TimelineEvent] Timeline event record data.

Code Example:

// WebInspector Command: Timeline.stop
Timeline.stop(function callback(res) {
	// res = {events}
});

Timeline.eventRecorded Event

Fired for every instrumentation event while timeline is started.

record
Timeline.TimelineEvent Timeline event record data.

Code Example:

// WebInspector Event: Timeline.eventRecorded
function onEventRecorded(res) {
	// res = {record}
}

Timeline.started Event

Fired when timeline is started.

consoleTimeline (optional)
Boolean If specified, identifies that timeline was started using console.timeline() call.

Code Example:

// WebInspector Event: Timeline.started
function onStarted(res) {
	// res = {consoleTimeline}
}

Timeline.stopped Event

Fired when timeline is stopped.

consoleTimeline (optional)
Boolean If specified, identifies that timeline was started using console.timeline() call.

Code Example:

// WebInspector Event: Timeline.stopped
function onStopped(res) {
	// res = {consoleTimeline}
}

Tracing

Command Event

Tracing.start Command

Strart trace events collection.

categories
String Category/tag filter

Code Example:

// WebInspector Command: Tracing.start
Tracing.start(categories);

Tracing.end Command

Stop trace events collection.

Code Example:

// WebInspector Command: Tracing.end
Tracing.end();

Tracing.dataCollected Event

value
[Object]

Code Example:

// WebInspector Event: Tracing.dataCollected
function onDataCollected(res) {
	// res = {value}
}

Tracing.tracingComplete Event

Code Example:

// WebInspector Event: Tracing.tracingComplete
function onTracingComplete(res) {
	// res = {}
}

Worker

Command Event

Worker.enable Command

Code Example:

// WebInspector Command: Worker.enable
Worker.enable();

Worker.disable Command

Code Example:

// WebInspector Command: Worker.disable
Worker.disable();

Worker.sendMessageToWorker Command

workerId
Integer
message
Object

Code Example:

// WebInspector Command: Worker.sendMessageToWorker
Worker.sendMessageToWorker(workerId, message);

Worker.canInspectWorkers Command

Tells whether browser supports workers inspection.

Callback Parameters:

result
Boolean True if browser has workers support.

Code Example:

// WebInspector Command: Worker.canInspectWorkers
Worker.canInspectWorkers(function callback(res) {
	// res = {result}
});

Worker.connectToWorker Command

workerId
Integer

Code Example:

// WebInspector Command: Worker.connectToWorker
Worker.connectToWorker(workerId);

Worker.disconnectFromWorker Command

workerId
Integer

Code Example:

// WebInspector Command: Worker.disconnectFromWorker
Worker.disconnectFromWorker(workerId);

Worker.setAutoconnectToWorkers Command

value
Boolean

Code Example:

// WebInspector Command: Worker.setAutoconnectToWorkers
Worker.setAutoconnectToWorkers(value);

Worker.workerCreated Event

workerId
Integer
url
String
inspectorConnected
Boolean

Code Example:

// WebInspector Event: Worker.workerCreated
function onWorkerCreated(res) {
	// res = {workerId, url, inspectorConnected}
}

Worker.workerTerminated Event

workerId
Integer

Code Example:

// WebInspector Event: Worker.workerTerminated
function onWorkerTerminated(res) {
	// res = {workerId}
}

Worker.dispatchMessageFromWorker Event

workerId
Integer
message
Object

Code Example:

// WebInspector Event: Worker.dispatchMessageFromWorker
function onDispatchMessageFromWorker(res) {
	// res = {workerId, message}
}

Worker.disconnectedFromWorker Event

Code Example:

// WebInspector Event: Worker.disconnectedFromWorker
function onDisconnectedFromWorker(res) {
	// res = {}
}

Generated from Inspector.json v1.1 on 2014-12-08 11:14:07-0800

Implementation by Jonathan Diehl

================================================ FILE: src/LiveDevelopment/Inspector/jsdoc.rb ================================================ #!/bin/ruby # # # Copyright (c) 2012 - present Adobe Systems Incorporated. All rights reserved. # # Permission is hereby granted, free of charge, to any person obtaining a # copy of this software and associated documentation files (the "Software"), # to deal in the Software without restriction, including without limitation # the rights to use, copy, modify, merge, publish, distribute, sublicense, # and/or sell copies of the Software, and to permit persons to whom the # Software is furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. # # # This script generates debugger documentation from Inspector.json # # Required Ruby Gems # json date open-uri require "rubygems" require "json" require "date" require "open-uri" #INSPECTOR_URL = "http://svn.webkit.org/repository/webkit/trunk/Source/WebCore/inspector/Inspector.json" INSPECTOR_URL = "Inspector.json" OUTPUT = "inspector.html" class NilClass def empty? true end def each end end class String def upcaseFirst self[0, 1].upcase + self[1, self.length - 1] end end class JSDoc def typedef(domain, info) return "[" + typedef(domain, info['items']) + "]" if info['items'] if info["$ref"] ref = info["$ref"] ref = domain + "." + ref unless ref =~ /\./ r = "#{ref}" if info["$ref"] elsif info["enum"] r = "( #{info['enum'].join " | "} )" else r = info["type"].upcaseFirst end r end def initialize(input, output) @input = input @output = output end def open @in = JSON.parse(File.open(@input).read) @in["domains"].sort! { |a,b| a["domain"] <=> b["domain"] } @version = "#{@in['version']['major']}.#{@in['version']['minor']}" File.open(@output, "w") do |out| @out = out yield end end def run open do writeDocument do writeTOC @in["domains"].each { |domain| writeDomain domain } end end end def write(*args) args.each { |line| @out.write line + "\n" } end def writeParams(domain, params, prefixLine = false) return unless params write prefixLine if prefixLine write "
" params.each do |p| name = p["name"] description = " #{p['description']}" if p['description'] type = typedef domain, p name += " (optional)" if p['optional'] write "
#{name}
" write "
#{type}#{description}
" end write "
" end def writeTOCLine(domain, info) info["name"] ||= info["id"] uid = "#{domain}.#{info['name']}" description = info['description'] ? ": #{info['description'].gsub(/<[^>]*>/, "")}" : "" write "
  • #{uid}#{description}
  • " end def writeTOCDomain(info) domain = info["domain"] unless info["types"].empty? write "Type", "
      " info["types"].each { |p| writeTOCLine domain, p } write "
    " end unless info["commands"].empty? write "Command", "
      " info["commands"].each { |p| writeTOCLine domain, p } write "
    " end unless info["events"].empty? write "Event", "
      " info["events"].each { |p| writeTOCLine domain, p } write "
    " end end def writeDocument @out.write <<-eos Inspector #{@version} Documentation ", "
    " yield write "
    ", "
    " write "

    Generated from Inspector.json v#{@version} on #{DateTime.now.strftime '%F %T%z'}

    " write "

    Implementation by Jonathan Diehl

    " write "
    ", "", "" end def writeTOC write "
    ", "

    Table of Contents

    " @in["domains"].each do |info| domain = info["domain"] write "

    #{info['domain']}

    " writeTOCDomain info end write "
    " end def writeDomain(info) domain, description = info["domain"], info["description"] types, commands, events = info["types"], info["commands"], info["events"] write "
    " write "

    #{domain}

    " write "

    #{description}

    " writeTOCDomain info if types types.each { |info| writeType domain, info } end if commands commands.each { |info| writeCommand domain, info } end if events events.each { |info| writeEvent domain, info } end write "
    " end def writeType(domain, info) name, description = info["id"], info["description"] type, enum, properties = info["type"], info["enum"], info["properties"] write "
    " write "

    #{domain}.#{name} Type

    " write "

    #{description}

    " if description if type != "object" write "
    " write "
    #{typedef domain, info}
    " write "
    " end writeParams domain, properties write "
    " end def writeCommand(domain, info) name, description = info["name"], info["description"] parameters, returns = info["parameters"], info["returns"] write "
    " write "

    #{domain}.#{name} Command

    " write "

    #{description}

    " if description writeParams domain, parameters writeParams domain, returns, "

    Callback Parameters:

    " write "

    Code Example:

    ", "
    "
    		paramNames = parameters.collect { |p| p["name"] } if parameters
    		paramNames ||= []
    		if returns
    			returnNames = returns.collect { |p| p["name"] }
    			paramNames << "function callback(res) {\n\t// res = {#{returnNames.join ", "}}\n}"
    		end
    		write "// WebInspector Command: #{domain}.#{name}"
    		write "#{domain}.#{name}(#{paramNames.join ", "});"
    		write "
    ", "
    " end def writeEvent(domain, info) name, description = info["name"], info["description"] parameters = info["parameters"] write "
    " write "

    #{domain}.#{name} Event

    " write "

    #{description}

    " if description writeParams domain, parameters write "

    Code Example:

    ", "
    "
    		paramNames = parameters.collect { |p| p["name"] } if parameters
    		paramNames ||= []
    		write "// WebInspector Event: #{domain}.#{name}"
    		write "function on#{name.upcaseFirst}(res) {\n\t// res = {#{paramNames.join ", "}}\n}"
    		write "
    ", "
    " end end jsdoc = JSDoc.new INSPECTOR_URL, OUTPUT jsdoc.run ================================================ FILE: src/LiveDevelopment/LiveDevMultiBrowser.js ================================================ /* * Copyright (c) 2012 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /** * LiveDevelopment allows Brackets to launch a browser with a "live preview" that's * connected to the current editor. * * # STARTING * * To start a session call `open`. This will read the currentDocument from brackets, * launch it in the default browser, and connect to it for live editing. * * # STOPPING * * To stop a session call `close`. This will close the connection to the browser * (but will not close the browser tab). * * # STATUS * * Status updates are dispatched as `statusChange` jQuery events. The status * is passed as the first parameter and the reason for the change as the second * parameter. Currently only the "Inactive" status supports the reason parameter. * The status codes are: * * 0: Inactive * 1: Connecting (waiting for a browser connection) * 2: Active * 3: Out of sync * 4: Sync error * 5: Reloading (after saving JS changes) * 6: Restarting (switching context to a new HTML live doc) * * The reason codes are: * - null (Unknown reason) * - "explicit_close" (LiveDevelopment.close() was called) * - "navigated_away" (The browser changed to a location outside of the project) * - "detached_target_closed" (The tab or window was closed) */ define(function (require, exports, module) { "use strict"; // Status Codes var STATUS_INACTIVE = exports.STATUS_INACTIVE = 0; var STATUS_CONNECTING = exports.STATUS_CONNECTING = 1; var STATUS_ACTIVE = exports.STATUS_ACTIVE = 2; var STATUS_OUT_OF_SYNC = exports.STATUS_OUT_OF_SYNC = 3; var STATUS_SYNC_ERROR = exports.STATUS_SYNC_ERROR = 4; var STATUS_RELOADING = exports.STATUS_RELOADING = 5; var STATUS_RESTARTING = exports.STATUS_RESTARTING = 6; var CommandManager = require("command/CommandManager"), Commands = require("command/Commands"), Dialogs = require("widgets/Dialogs"), DefaultDialogs = require("widgets/DefaultDialogs"), DocumentManager = require("document/DocumentManager"), EditorManager = require("editor/EditorManager"), EventDispatcher = require("utils/EventDispatcher"), FileUtils = require("file/FileUtils"), MainViewManager = require("view/MainViewManager"), PreferencesDialogs = require("preferences/PreferencesDialogs"), ProjectManager = require("project/ProjectManager"), Strings = require("strings"), _ = require("thirdparty/lodash"), LiveDevelopmentUtils = require("LiveDevelopment/LiveDevelopmentUtils"), LiveDevServerManager = require("LiveDevelopment/LiveDevServerManager"), NodeSocketTransport = require("LiveDevelopment/MultiBrowserImpl/transports/NodeSocketTransport"), LiveDevProtocol = require("LiveDevelopment/MultiBrowserImpl/protocol/LiveDevProtocol"), DefaultLauncher = require("LiveDevelopment/MultiBrowserImpl/launchers/Launcher"); // Documents var LiveCSSDocument = require("LiveDevelopment/MultiBrowserImpl/documents/LiveCSSDocument"), LiveHTMLDocument = require("LiveDevelopment/MultiBrowserImpl/documents/LiveHTMLDocument"); /** * @private * The live HTML document for the currently active preview. * @type {LiveHTMLDocument} */ var _liveDocument; /** * @private * Live documents related to the active HTML document - for example, CSS files * that are used by the document. * @type {Object.} */ var _relatedDocuments = {}; /** * @private * Protocol handler that provides the actual live development API on top of the current transport. */ var _protocol = LiveDevProtocol; /** * @private * Current browser launcher for preview. */ var _launcher; /** * @private * Current live preview server * @type {BaseServer} */ var _server; /** * @private * Determine which live document class should be used for a given document * @param {Document} document The document we want to create a live document for. * @return {function} The constructor for the live document class; will be a subclass of LiveDocument. */ function _classForDocument(doc) { if (doc.getLanguage().getId() === "css") { return LiveCSSDocument; } if (LiveDevelopmentUtils.isHtmlFileExt(doc.file.fullPath)) { return LiveHTMLDocument; } return null; } /** * Returns true if the global Live Development mode is on (might be in the middle of connecting). * @return {boolean} */ function isActive() { return exports.status > STATUS_INACTIVE; } /** * Returns the live document for a given path, or null if there is no live document for it. * @param {string} path * @return {?LiveDocument} */ function getLiveDocForPath(path) { if (!_server) { return null; } return _server.get(path); } /** * @private * Close a live document. * @param {LiveDocument} */ function _closeDocument(liveDocument) { liveDocument.off(".livedev"); liveDocument.close(); } /** * Removes the given CSS/JSDocument from _relatedDocuments. Signals that the * given file is no longer associated with the HTML document that is live (e.g. * if the related file has been deleted on disk). * @param {string} url Absolute URL of the related document */ function _handleRelatedDocumentDeleted(url) { var liveDoc = _relatedDocuments[url]; if (liveDoc) { delete _relatedDocuments[url]; } if (_server) { _server.remove(liveDoc); } _closeDocument(liveDoc); } /** * Update the status. Triggers a statusChange event. * @param {number} status new status * @param {?string} closeReason Optional string key suffix to display to * user when closing the live development connection (see LIVE_DEV_* keys) */ function _setStatus(status, closeReason) { // Don't send a notification when the status didn't actually change if (status === exports.status) { return; } exports.status = status; var reason = status === STATUS_INACTIVE ? closeReason : null; exports.trigger("statusChange", status, reason); } /** * @private * Close all live documents. */ function _closeDocuments() { if (_liveDocument) { _closeDocument(_liveDocument); _liveDocument = undefined; } Object.keys(_relatedDocuments).forEach(function (url) { _closeDocument(_relatedDocuments[url]); delete _relatedDocuments[url]; }); // Clear all documents from request filtering if (_server) { _server.clear(); } } /** * @private * Returns the URL that we would serve the given path at. * @param {string} path * @return {string} */ function _resolveUrl(path) { return _server && _server.pathToUrl(path); } /** * @private * Create a LiveDocument for a Brackets editor/document to manage communication between the * editor and the browser. * @param {Document} doc * @param {Editor} editor * @param {roots} roots * @return {?LiveDocument} The live document, or null if this type of file doesn't support live editing. */ function _createLiveDocument(doc, editor, roots) { var DocClass = _classForDocument(doc), liveDocument; if (!DocClass) { return null; } liveDocument = new DocClass(_protocol, _resolveUrl, doc, editor, roots); liveDocument.on("errorStatusChanged.livedev", function (event, hasErrors) { if (isActive()) { _setStatus(hasErrors ? STATUS_SYNC_ERROR : STATUS_ACTIVE); } }); return liveDocument; } /** * Documents are considered to be out-of-sync if they are dirty and * do not have "update while editing" support * @param {Document} doc * @return {boolean} */ function _docIsOutOfSync(doc) { var liveDoc = _server && _server.get(doc.file.fullPath), isLiveEditingEnabled = liveDoc && liveDoc.isLiveEditingEnabled(); return doc.isDirty && !isLiveEditingEnabled; } /** * Handles a notification from the browser that a stylesheet was loaded into * the live HTML document. If the stylesheet maps to a file in the project, then * creates a live document for the stylesheet and adds it to _relatedDocuments. * @param {$.Event} event * @param {string} url The URL of the stylesheet that was added. * @param {array} roots The URLs of the roots of the stylesheet (the css files loaded through ) */ function _styleSheetAdded(event, url, roots) { var path = _server && _server.urlToPath(url), alreadyAdded = !!_relatedDocuments[url]; // path may be null if loading an external stylesheet. // Also, the stylesheet may already exist and be reported as added twice // due to Chrome reporting added/removed events after incremental changes // are pushed to the browser if (!path || alreadyAdded) { return; } var docPromise = DocumentManager.getDocumentForPath(path); docPromise.done(function (doc) { if ((_classForDocument(doc) === LiveCSSDocument) && (!_liveDocument || (doc !== _liveDocument.doc))) { var liveDoc = _createLiveDocument(doc, doc._masterEditor, roots); if (liveDoc) { _server.add(liveDoc); _relatedDocuments[doc.url] = liveDoc; liveDoc.on("updateDoc", function (event, url) { var path = _server.urlToPath(url), doc = getLiveDocForPath(path); doc._updateBrowser(); }); } } }); } /** * @private * Determine an index file that can be used to start Live Development. * This function will inspect all files in a project to find the closest index file * available for currently opened document. We are searching for these files: * - index.html * - index.htm * * If the project is configured with a custom base url for live development, then * the list of possible index files is extended to contain these index files too: * - index.php * - index.php3 * - index.php4 * - index.php5 * - index.phtm * - index.phtml * - index.cfm * - index.cfml * - index.asp * - index.aspx * - index.jsp * - index.jspx * - index.shm * - index.shml * * If a file was found, the promise will be resolved with the full path to this file. If no file * was found in the whole project tree, the promise will be resolved with null. * * @return {jQuery.Promise} A promise that is resolved with a full path * to a file if one could been determined, or null if there was no suitable index * file. */ function _getInitialDocFromCurrent() { var doc = DocumentManager.getCurrentDocument(), refPath, i; // Is the currently opened document already a file we can use for Live Development? if (doc) { refPath = doc.file.fullPath; if (LiveDevelopmentUtils.isStaticHtmlFileExt(refPath) || LiveDevelopmentUtils.isServerHtmlFileExt(refPath)) { return new $.Deferred().resolve(doc); } } var result = new $.Deferred(); var baseUrl = ProjectManager.getBaseUrl(), hasOwnServerForLiveDevelopment = (baseUrl && baseUrl.length); ProjectManager.getAllFiles().done(function (allFiles) { var projectRoot = ProjectManager.getProjectRoot().fullPath, containingFolder, indexFileFound = false, stillInProjectTree = true; if (refPath) { containingFolder = FileUtils.getDirectoryPath(refPath); } else { containingFolder = projectRoot; } var filteredFiltered = allFiles.filter(function (item) { var parent = FileUtils.getParentPath(item.fullPath); return (containingFolder.indexOf(parent) === 0); }); var filterIndexFile = function (fileInfo) { if (fileInfo.fullPath.indexOf(containingFolder) === 0) { if (FileUtils.getFilenameWithoutExtension(fileInfo.name) === "index") { if (hasOwnServerForLiveDevelopment) { if ((LiveDevelopmentUtils.isServerHtmlFileExt(fileInfo.name)) || (LiveDevelopmentUtils.isStaticHtmlFileExt(fileInfo.name))) { return true; } } else if (LiveDevelopmentUtils.isStaticHtmlFileExt(fileInfo.name)) { return true; } } else { return false; } } }; while (!indexFileFound && stillInProjectTree) { i = _.findIndex(filteredFiltered, filterIndexFile); // We found no good match if (i === -1) { // traverse the directory tree up one level containingFolder = FileUtils.getParentPath(containingFolder); // Are we still inside the project? if (containingFolder.indexOf(projectRoot) === -1) { stillInProjectTree = false; } } else { indexFileFound = true; } } if (i !== -1) { DocumentManager.getDocumentForPath(filteredFiltered[i].fullPath).then(result.resolve, result.resolve); return; } result.resolve(null); }); return result.promise(); } /** * @private * Close the connection and the associated window * @param {boolean} doCloseWindow Use true to close the window/tab in the browser * @param {?string} reason Optional string key suffix to display to user (see LIVE_DEV_* keys) */ function _close(doCloseWindow, reason) { if (exports.status !== STATUS_INACTIVE) { // Close live documents _closeDocuments(); // Close all active connections _protocol.closeAllConnections(); if (_server) { // Stop listening for requests when disconnected _server.stop(); // Dispose server _server = null; } } //TODO: implement closeWindow together with launchers. // if (doCloseWindow) { // // } _setStatus(STATUS_INACTIVE, reason || "explicit_close"); } /** * Closes all active connections. * Returns a resolved promise for API compatibility. * @return {$.Promise} A resolved promise */ function close() { _close(true); return new $.Deferred().resolve().promise(); } /** * @private * Displays an error when no HTML file can be found to preview. */ function _showWrongDocError() { Dialogs.showModalDialog( DefaultDialogs.DIALOG_ID_ERROR, Strings.LIVE_DEVELOPMENT_ERROR_TITLE, Strings.LIVE_DEV_NEED_HTML_MESSAGE ); } /** * @private * Displays an error when the server for live development files can't be started. */ function _showLiveDevServerNotReadyError() { Dialogs.showModalDialog( DefaultDialogs.DIALOG_ID_ERROR, Strings.LIVE_DEVELOPMENT_ERROR_TITLE, Strings.LIVE_DEV_SERVER_NOT_READY_MESSAGE ); } /** * @private * Creates the main live document for a given HTML document and notifies the server it exists. * TODO: we should really maintain the list of live documents, not the server. * @param {Document} doc */ function _createLiveDocumentForFrame(doc) { // create live document doc._ensureMasterEditor(); _liveDocument = _createLiveDocument(doc, doc._masterEditor); _server.add(_liveDocument); } /** * Launches the given URL in the default browser. * @param {string} url * TODO: launchers for multiple browsers */ function _launch(url) { // open default browser // TODO: fail? // _launcher.launch(url); } /** * @private * Launches the given document in the browser, given that a live document has already * been created for it. * @param {Document} doc */ function _open(doc) { if (doc && _liveDocument && doc === _liveDocument.doc) { if (_server) { // Launch the URL in the browser. If it's the first one to connect back to us, // our status will transition to ACTIVE once it does so. if (exports.status < STATUS_ACTIVE) { _launch(_server.pathToUrl(doc.file.fullPath)); } if (exports.status === STATUS_RESTARTING) { // change page in browser _protocol.navigate(_server.pathToUrl(doc.file.fullPath)); } _protocol // TODO: timeout if we don't get a connection within a certain time .on("ConnectionConnect.livedev", function (event, msg) { // check for the first connection if (_protocol.getConnectionIds().length === 1) { // check the page that connection comes from matches the current live document session if (_liveDocument && (msg.url === _resolveUrl(_liveDocument.doc.file.fullPath))) { _setStatus(STATUS_ACTIVE); } } }) .on("ConnectionClose.livedev", function (event, msg) { // close session when the last connection was closed if (_protocol.getConnectionIds().length === 0) { setTimeout(function () { if (_protocol.getConnectionIds().length === 0 && exports.status <= STATUS_ACTIVE) { _close(false, "detached_target_closed"); } }, 5000); } }) // extract stylesheets and create related LiveCSSDocument instances .on("DocumentRelated.livedev", function (event, msg) { var relatedDocs = msg.related; var docs = Object.keys(relatedDocs.stylesheets); docs.forEach(function (url) { _styleSheetAdded(null, url, relatedDocs.stylesheets[url]); }); }) // create new LiveCSSDocument if a new stylesheet is added .on("StylesheetAdded.livedev", function (event, msg) { _styleSheetAdded(null, msg.href, msg.roots); }) // remove LiveCSSDocument instance when stylesheet is removed .on("StylesheetRemoved.livedev", function (event, msg) { _handleRelatedDocumentDeleted(msg.href); }); } else { console.error("LiveDevelopment._open(): No server active"); } } else { // Unlikely that we would get to this state where // a connection is in process but there is no current // document close(); } } /** * @private * Creates the live document in preparation for launching the * preview of the given document, then launches it. (The live document * must already exist before we launch it so that the server can * ask it for the instrumented version of the document when the browser * requests it.) * TODO: could probably just consolidate this with _open() * @param {Document} doc */ function _doLaunchAfterServerReady(initialDoc) { _createLiveDocumentForFrame(initialDoc); // start listening for requests _server.start(); // open browser to the url _open(initialDoc); } /** * @private * Create the server in preparation for opening a live preview. * @param {Document} doc The document we want the server for. Different servers handle * different types of project (a static server for when no app server is configured, * vs. a user server when there is an app server set in File > Project Settings). */ function _prepareServer(doc) { var deferred = new $.Deferred(), showBaseUrlPrompt = false; _server = LiveDevServerManager.getServer(doc.file.fullPath); // Optionally prompt for a base URL if no server was found but the // file is a known server file extension showBaseUrlPrompt = !_server && LiveDevelopmentUtils.isServerHtmlFileExt(doc.file.fullPath); if (showBaseUrlPrompt) { // Prompt for a base URL PreferencesDialogs.showProjectPreferencesDialog("", Strings.LIVE_DEV_NEED_BASEURL_MESSAGE) .done(function (id) { if (id === Dialogs.DIALOG_BTN_OK && ProjectManager.getBaseUrl()) { // If base url is specifed, then re-invoke _prepareServer() to continue _prepareServer(doc).then(deferred.resolve, deferred.reject); } else { deferred.reject(); } }); } else if (_server) { // Startup the server var readyPromise = _server.readyToServe(); if (!readyPromise) { _showLiveDevServerNotReadyError(); deferred.reject(); } else { readyPromise.then(deferred.resolve, function () { _showLiveDevServerNotReadyError(); deferred.reject(); }); } } else { // No server found deferred.reject(); } return deferred.promise(); } /** * @private * MainViewManager.currentFileChange event handler. * When switching documents, close the current preview and open a new one. */ function _onFileChange() { var doc = DocumentManager.getCurrentDocument(); if (!isActive() || !doc) { return; } // close the current session and begin a new session var docUrl = _server && _server.pathToUrl(doc.file.fullPath), isViewable = _server && _server.canServe(doc.file.fullPath); if (_liveDocument.doc.url !== docUrl && isViewable) { // clear live doc and related docs _closeDocuments(); // create new live doc _createLiveDocumentForFrame(doc); _setStatus(STATUS_RESTARTING); _open(doc); } } /** * Open a live preview on the current docuemnt. */ function open() { // TODO: need to run _onDocumentChange() after load if doc != currentDocument here? Maybe not, since activeEditorChange // doesn't trigger it, while inline editors can still cause edits in doc other than currentDoc... _getInitialDocFromCurrent().done(function (doc) { var prepareServerPromise = (doc && _prepareServer(doc)) || new $.Deferred().reject(), otherDocumentsInWorkingFiles; if (doc && !doc._masterEditor) { otherDocumentsInWorkingFiles = MainViewManager.getWorkingSetSize(MainViewManager.ALL_PANES); MainViewManager.addToWorkingSet(MainViewManager.ACTIVE_PANE, doc.file); if (!otherDocumentsInWorkingFiles) { CommandManager.execute(Commands.CMD_OPEN, { fullPath: doc.file.fullPath }); } } // wait for server (StaticServer, Base URL or file:) prepareServerPromise .done(function () { _setStatus(STATUS_CONNECTING); _doLaunchAfterServerReady(doc); }) .fail(function () { _showWrongDocError(); }); }); } /** * For files that don't support as-you-type live editing, but are loaded by live HTML documents * (e.g. JS files), we want to reload the full document when they're saved. * @param {$.Event} event * @param {Document} doc */ function _onDocumentSaved(event, doc) { if (!isActive() || !_server) { return; } var absolutePath = doc.file.fullPath, liveDocument = absolutePath && _server.get(absolutePath), liveEditingEnabled = liveDocument && liveDocument.isLiveEditingEnabled && liveDocument.isLiveEditingEnabled(); // Skip reload if the saved document has live editing enabled if (liveEditingEnabled) { return; } // reload the page if the given document is a JS file related // to the current live document. if (_liveDocument.isRelated(absolutePath)) { if (doc.getLanguage().getId() === "javascript") { _setStatus(STATUS_RELOADING); _protocol.reload(); } } } /** * For files that don't support as-you-type live editing, but are loaded by live HTML documents * (e.g. JS files), we want to show a dirty indicator on the live development icon when they * have unsaved changes, so the user knows s/he needs to save in order to have the page reload. * @param {$.Event} event * @param {Document} doc */ function _onDirtyFlagChange(event, doc) { if (!isActive() || !_server) { return; } var absolutePath = doc.file.fullPath; if (_liveDocument.isRelated(absolutePath)) { // Set status to out of sync if dirty. Otherwise, set it to active status. _setStatus(_docIsOutOfSync(doc) ? STATUS_OUT_OF_SYNC : STATUS_ACTIVE); } } /** * Sets the current transport mechanism to be used by the live development protocol * (e.g. socket server, iframe postMessage, etc.) * The low-level transport. Must provide the following methods: * * - start(): Initiates transport (eg. creates Web Socket server). * - send(idOrArray, string): Dispatches the given protocol message (provided as a JSON string) to the given client ID * or array of client IDs. (See the "connect" message for an explanation of client IDs.) * - close(id): Closes the connection to the given client ID. * - getRemoteScript(): Returns a script that should be injected into the page's HTML in order to handle the remote side * of the transport. Should include the "\n"; } /** * Returns a script that should be injected into the HTML that's launched in the * browser in order to handle protocol requests. Includes the \n" + remoteFunctionsScript; } /** * Protocol method. Evaluates the given script in the browser (in global context), and returns a promise * that will be fulfilled with the result of the script, if any. * @param {number|Array.} clients A client ID or array of client IDs that should evaluate * the script. * @param {string} script The script to evaluate. * @return {$.Promise} A promise that's resolved with the return value from the first client that responds * to the evaluation. */ function evaluate(script, clients) { return _send( { method: "Runtime.evaluate", params: { expression: script } }, clients ); } /** * Protocol method. Reloads a CSS styleseet in the browser (by replacing its text) given its url. * @param {string} url Absolute URL of the stylesheet * @param {string} text The new text of the stylesheet * @param {number|Array.} clients A client ID or array of client IDs that should evaluate * the script. * @return {$.Promise} A promise that's resolved with the return value from the first client that responds * to the evaluation. */ function setStylesheetText(url, text, clients) { return _send( { method: "CSS.setStylesheetText", params: { url: url, text: text } } ); } /** * Protocol method. Rretrieves the content of a given stylesheet (for unit testing) * @param {number|Array.} clients A client ID or array of client IDs that should navigate to the given URL. * @param {string} url Absolute URL that identifies the stylesheet. * @return {$.Promise} A promise that's resolved with the return value from the first client that responds * to the method. */ function getStylesheetText(url, clients) { return _send( { method: "CSS.getStylesheetText", params: { url: url } }, clients ); } /** * Protocol method. Reloads the page that is currently loaded into the browser, optionally ignoring cache. * @param {number|Array.} clients A client ID or array of client IDs that should reload the page. * @param {boolean} ignoreCache If true, browser cache is ignored. * @return {$.Promise} A promise that's resolved with the return value from the first client that responds * to the method. */ function reload(ignoreCache, clients) { return _send( { method: "Page.reload", params: { ignoreCache: true } }, clients ); } /** * Protocol method. Navigates current page to the given URL. * @param {number|Array.} clients A client ID or array of client IDs that should navigate to the given URL. * @param {string} url URL to navigate the page to. * @return {$.Promise} A promise that's resolved with the return value from the first client that responds * to the method. */ function navigate(url, clients) { return _send( { method: "Page.navigate", params: { url: url } }, clients ); } /** * Closes the connection to the given client. Proxies to the transport. * @param {number} clientId */ function close(clientId) { _transport.close(clientId); } function closeAllConnections() { getConnectionIds().forEach(function (clientId) { close(clientId); }); _connections = {}; } EventDispatcher.makeEventDispatcher(exports); // public API exports.setTransport = setTransport; exports.getRemoteScript = getRemoteScript; exports.evaluate = evaluate; exports.setStylesheetText = setStylesheetText; exports.getStylesheetText = getStylesheetText; exports.reload = reload; exports.navigate = navigate; exports.close = close; exports.getConnectionIds = getConnectionIds; exports.closeAllConnections = closeAllConnections; }); ================================================ FILE: src/LiveDevelopment/MultiBrowserImpl/protocol/remote/DocumentObserver.js ================================================ /* * Copyright (c) 2014 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /*global setInterval, clearInterval */ (function (global) { "use strict"; var ProtocolManager = global._Brackets_LiveDev_ProtocolManager; var _document = null; var _transport; /** * Retrieves related documents (external CSS and JS files) * * @return {{scripts: object, stylesheets: object}} Related scripts and stylesheets */ function related() { var rel = { scripts: {}, stylesheets: {} }; var i; // iterate on document scripts (HTMLCollection doesn't provide forEach iterator). for (i = 0; i < _document.scripts.length; i++) { // add only external scripts if (_document.scripts[i].src) { rel.scripts[_document.scripts[i].src] = true; } } var s, j; //traverse @import rules var traverseRules = function _traverseRules(sheet, base) { var i, href = sheet.href, cssRules; // Deal with Firefox's SecurityError when accessing sheets // from other domains. Chrome will safely return `undefined`. try { cssRules = sheet.cssRules; } catch (e) { if (e.name !== "SecurityError") { throw e; } } if (href && cssRules) { if (rel.stylesheets[href] === undefined) { rel.stylesheets[href] = []; } rel.stylesheets[href].push(base); for (i = 0; i < cssRules.length; i++) { if (cssRules[i].href) { traverseRules(cssRules[i].styleSheet, base); } } } }; //iterate on document.stylesheets (StyleSheetList doesn't provide forEach iterator). for (j = 0; j < window.document.styleSheets.length; j++) { s = window.document.styleSheets[j]; traverseRules(s, s.href); } return rel; } /** * Common functions. */ var Utils = { isExternalStylesheet: function (node) { return (node.nodeName.toUpperCase() === "LINK" && node.rel === "stylesheet" && node.href); }, isExternalScript: function (node) { return (node.nodeName.toUpperCase() === "SCRIPT" && node.src); } }; /** * CSS related commands and notifications */ var CSS = { /** * Maintains a map of stylesheets loaded thorugh @import rules and their parents. * Populated by extractImports, consumed by notifyImportsAdded / notifyImportsRemoved. * @type { */ stylesheets : {}, /** * Check the stylesheet that was just added be really loaded * to be able to extract potential import-ed stylesheets. * It invokes notifyStylesheetAdded once the sheet is loaded. * @param {string} href Absolute URL of the stylesheet. */ checkForStylesheetLoaded : function (href) { var self = this; // Inspect CSSRules for @imports: // styleSheet obejct is required to scan CSSImportRules but // browsers differ on the implementation of MutationObserver interface. // Webkit triggers notifications before stylesheets are loaded, // Firefox does it after loading. // There are also differences on when 'load' event is triggered for // the 'link' nodes. Webkit triggers it before stylesheet is loaded. // Some references to check: // http://www.phpied.com/when-is-a-stylesheet-really-loaded/ // http://stackoverflow.com/questions/17747616/webkit-dynamically-created-stylesheet-when-does-it-really-load // http://stackoverflow.com/questions/11425209/are-dom-mutation-observers-slower-than-dom-mutation-events // // TODO: This is just a temporary 'cross-browser' solution, it needs optimization. var loadInterval = setInterval(function () { var i; for (i = 0; i < window.document.styleSheets.length; i++) { if (window.document.styleSheets[i].href === href) { //clear interval clearInterval(loadInterval); // notify stylesheets added self.notifyStylesheetAdded(href); break; } } }, 50); }, onStylesheetRemoved : function (url) { // get style node created when setting new text for stylesheet. var s = window.document.getElementById(url); // remove if (s && s.parentNode && s.parentNode.removeChild) { s.parentNode.removeChild(s); } }, /** * Send a notification for the stylesheet added and * its import-ed styleshets based on document.stylesheets diff * from previous status. It also updates stylesheets status. */ notifyStylesheetAdded : function () { var added = {}, current, newStatus; current = this.stylesheets; newStatus = related().stylesheets; Object.keys(newStatus).forEach(function (v, i) { if (!current[v]) { added[v] = newStatus[v]; } }); Object.keys(added).forEach(function (v, i) { _transport.send(JSON.stringify({ method: "StylesheetAdded", href: v, roots: [added[v]] })); }); this.stylesheets = newStatus; }, /** * Send a notification for the removed stylesheet and * its import-ed styleshets based on document.stylesheets diff * from previous status. It also updates stylesheets status. */ notifyStylesheetRemoved : function () { var self = this; var removed = {}, newStatus, current; current = self.stylesheets; newStatus = related().stylesheets; Object.keys(current).forEach(function (v, i) { if (!newStatus[v]) { removed[v] = current[v]; // remove node created by setStylesheetText if any self.onStylesheetRemoved(current[v]); } }); Object.keys(removed).forEach(function (v, i) { _transport.send(JSON.stringify({ method: "StylesheetRemoved", href: v, roots: [removed[v]] })); }); self.stylesheets = newStatus; } }; /* process related docs added */ function _onNodesAdded(nodes) { var i; for (i = 0; i < nodes.length; i++) { //check for Javascript files if (Utils.isExternalScript(nodes[i])) { _transport.send(JSON.stringify({ method: 'ScriptAdded', src: nodes[i].src })); } //check for stylesheets if (Utils.isExternalStylesheet(nodes[i])) { CSS.checkForStylesheetLoaded(nodes[i].href); } } } /* process related docs removed */ function _onNodesRemoved(nodes) { var i; //iterate on removed nodes for (i = 0; i < nodes.length; i++) { // check for external JS files if (Utils.isExternalScript(nodes[i])) { _transport.send(JSON.stringify({ method: 'ScriptRemoved', src: nodes[i].src })); } //check for external StyleSheets if (Utils.isExternalStylesheet(nodes[i])) { CSS.notifyStylesheetRemoved(nodes[i].href); } } } function _enableListeners() { // enable MutationOberver if it's supported var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; if (MutationObserver) { var observer = new MutationObserver(function (mutations) { mutations.forEach(function (mutation) { if (mutation.addedNodes.length > 0) { _onNodesAdded(mutation.addedNodes); } if (mutation.removedNodes.length > 0) { _onNodesRemoved(mutation.removedNodes); } }); }); observer.observe(_document, { childList: true, subtree: true }); } else { // use MutationEvents as fallback window.document.addEventListener('DOMNodeInserted', function niLstnr(e) { _onNodesAdded([e.target]); }); window.document.addEventListener('DOMNodeRemoved', function nrLstnr(e) { _onNodesRemoved([e.target]); }); } } /** * Start listening for events and send initial related documents message. * * @param {HTMLDocument} document * @param {object} transport Live development transport connection */ function start(document, transport) { _transport = transport; _document = document; // start listening to node changes _enableListeners(); var rel = related(); // send the current status of related docs. _transport.send(JSON.stringify({ method: "DocumentRelated", related: rel })); // initialize stylesheets with current status for further notifications. CSS.stylesheets = rel.stylesheets; } /** * Stop listening. * TODO currently a no-op. */ function stop() { } var DocumentObserver = { start: start, stop: stop, related: related }; ProtocolManager.setDocumentObserver(DocumentObserver); }(this)); ================================================ FILE: src/LiveDevelopment/MultiBrowserImpl/protocol/remote/LiveDevProtocolRemote.js ================================================ /* * Copyright (c) 2014 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /*jslint evil: true */ // This is the script that Brackets live development injects into HTML pages in order to // establish and maintain the live development socket connection. Note that Brackets may // also inject other scripts via "evaluate" once this has connected back to Brackets. (function (global) { "use strict"; // This protocol handler assumes that there is also an injected transport script that // has the following methods: // setCallbacks(obj) - a method that takes an object with a "message" callback that // will be called with the message string whenever a message is received by the transport. // send(msgStr) - sends the given message string over the transport. var transport = global._Brackets_LiveDev_Transport; /** * Manage messaging between Editor and Browser at the protocol layer. * Handle messages that arrives through the current transport and dispatch them * to subscribers. Subscribers are handlers that implements remote commands/functions. * Property 'method' of messages body is used as the 'key' to identify message types. * Provide a 'send' operation that allows remote commands sending messages to the Editor. */ var MessageBroker = { /** * Collection of handlers (subscribers) per each method. * To be pushed by 'on' and consumed by 'trigger' stored this way: * handlers[method] = [handler1, handler2, ...] */ handlers: {}, /** * Dispatch messages to handlers according to msg.method value. * @param {Object} msg Message to be dispatched. */ trigger: function (msg) { var msgHandlers; if (!msg.method) { // no message type, ignoring it // TODO: should we trigger a generic event? console.log("[Brackets LiveDev] Received message without method."); return; } // get handlers for msg.method msgHandlers = this.handlers[msg.method]; if (msgHandlers && msgHandlers.length > 0) { // invoke handlers with the received message msgHandlers.forEach(function (handler) { try { // TODO: check which context should be used to call handlers here. handler(msg); return; } catch (e) { console.log("[Brackets LiveDev] Error executing a handler for " + msg.method); console.log(e.stack); return; } }); } else { // no subscribers, ignore it. // TODO: any other default handling? (eg. specific respond, trigger as a generic event, etc.); console.log("[Brackets LiveDev] No subscribers for message " + msg.method); return; } }, /** * Send a response of a particular message to the Editor. * Original message must provide an 'id' property * @param {Object} orig Original message. * @param {Object} response Message to be sent as the response. */ respond: function (orig, response) { if (!orig.id) { console.log("[Brackets LiveDev] Trying to send a response for a message with no ID"); return; } response.id = orig.id; this.send(response); }, /** * Subscribe handlers to specific messages. * @param {string} method Message type. * @param {function} handler. * TODO: add handler name or any identification mechanism to then implement 'off'? */ on: function (method, handler) { if (!method || !handler) { return; } if (!this.handlers[method]) { //initialize array this.handlers[method] = []; } // add handler to the stack this.handlers[method].push(handler); }, /** * Send a message to the Editor. * @param {string} msgStr Message to be sent. */ send: function (msgStr) { transport.send(JSON.stringify(msgStr)); } }; /** * Runtime Domain. Implements remote commands for "Runtime.*" */ var Runtime = { /** * Evaluate an expresion and return its result. */ evaluate: function (msg) { console.log("Runtime.evaluate"); var result = eval(msg.params.expression); MessageBroker.respond(msg, { result: JSON.stringify(result) // TODO: in original protocol this is an object handle }); } }; // subscribe handler to method Runtime.evaluate MessageBroker.on("Runtime.evaluate", Runtime.evaluate); /** * CSS Domain. */ var CSS = { setStylesheetText : function (msg) { if (!msg || !msg.params || !msg.params.text || !msg.params.url) { return; } var i, node; var head = window.document.getElementsByTagName('head')[0]; // create an style element to replace the one loaded with var s = window.document.createElement('style'); s.type = 'text/css'; s.appendChild(window.document.createTextNode(msg.params.text)); for (i = 0; i < window.document.styleSheets.length; i++) { node = window.document.styleSheets[i]; if (node.ownerNode.id === msg.params.url) { head.insertBefore(s, node.ownerNode); // insert the style element here // now can remove the style element previously created (if any) node.ownerNode.parentNode.removeChild(node.ownerNode); } else if (node.href === msg.params.url && !node.disabled) { // if the link element to change head.insertBefore(s, node.ownerNode); // insert the style element here node.disabled = true; i++; // since we have just inserted a stylesheet } } s.id = msg.params.url; }, /** * retrieves the content of the stylesheet * TODO: it now depends on reloadCSS implementation */ getStylesheetText: function (msg) { var i, sheet, text = ""; for (i = 0; i < window.document.styleSheets.length; i++) { sheet = window.document.styleSheets[i]; // if it was already 'reloaded' if (sheet.ownerNode.id === msg.params.url) { text = sheet.ownerNode.textContent; } else if (sheet.href === msg.params.url && !sheet.disabled) { var j, rules; // Deal with Firefox's SecurityError when accessing sheets // from other domains, and Chrome returning `undefined`. try { rules = window.document.styleSheets[i].cssRules; } catch (e) { if (e.name !== "SecurityError") { throw e; } } if (!rules) { return; } for (j = 0; j < rules.length; j++) { text += rules[j].cssText + '\n'; } } } MessageBroker.respond(msg, { text: text }); } }; MessageBroker.on("CSS.setStylesheetText", CSS.setStylesheetText); MessageBroker.on("CSS.getStylesheetText", CSS.getStylesheetText); /** * Page Domain. */ var Page = { /** * Reload the current page optionally ignoring cache. * @param {Object} msg */ reload: function (msg) { // just reload the page window.location.reload(msg.params.ignoreCache); }, /** * Navigate to a different page. * @param {Object} msg */ navigate: function (msg) { if (msg.params.url) { // navigate to a new page. window.location.replace(msg.params.url); } } }; // subscribe handler to method Page.reload MessageBroker.on("Page.reload", Page.reload); MessageBroker.on("Page.navigate", Page.navigate); MessageBroker.on("ConnectionClose", Page.close); // By the time this executes, there must already be an active transport. if (!transport) { console.error("[Brackets LiveDev] No transport set"); return; } var ProtocolManager = { _documentObserver: {}, _protocolHandler: {}, enable: function () { transport.setCallbacks(this._protocolHandler); transport.enable(); }, onConnect: function () { this._documentObserver.start(window.document, transport); }, onClose: function () { var body = window.document.getElementsByTagName("body")[0], overlay = window.document.createElement("div"), background = window.document.createElement("div"), status = window.document.createElement("div"); overlay.style.width = "100%"; overlay.style.height = "100%"; overlay.style.zIndex = 2227; overlay.style.position = "fixed"; overlay.style.top = 0; overlay.style.left = 0; background.style.backgroundColor = "#fff"; background.style.opacity = 0.5; background.style.width = "100%"; background.style.height = "100%"; background.style.position = "fixed"; background.style.top = 0; background.style.left = 0; status.textContent = "Live Development Session has Ended"; status.style.width = "100%"; status.style.color = "#fff"; status.style.backgroundColor = "#666"; status.style.position = "fixed"; status.style.top = 0; status.style.left = 0; status.style.padding = "0.2em"; status.style.verticalAlign = "top"; status.style.textAlign = "center"; overlay.appendChild(background); overlay.appendChild(status); body.appendChild(overlay); // change the title as well window.document.title = "(Brackets Live Preview: closed) " + window.document.title; }, setDocumentObserver: function (documentOberver) { if (!documentOberver) { return; } this._documentObserver = documentOberver; }, setProtocolHandler: function (protocolHandler) { if (!protocolHandler) { return; } this._protocolHandler = protocolHandler; } }; // exposing ProtocolManager global._Brackets_LiveDev_ProtocolManager = ProtocolManager; /** * The remote handler for the protocol. */ var ProtocolHandler = { /** * Handles a message from the transport. Parses it as JSON and delegates * to MessageBroker who is in charge of routing them to handlers. * @param {string} msgStr The protocol message as stringified JSON. */ message: function (msgStr) { var msg; try { msg = JSON.parse(msgStr); } catch (e) { console.log("[Brackets LiveDev] Malformed message received: ", msgStr); return; } // delegates handling/routing to MessageBroker. MessageBroker.trigger(msg); }, close: function (evt) { ProtocolManager.onClose(); }, connect: function (evt) { ProtocolManager.onConnect(); } }; ProtocolManager.setProtocolHandler(ProtocolHandler); window.addEventListener('load', function () { ProtocolManager.enable(); }); /** * Sends the message containing tagID which is being clicked * to the editor in order to change the cursor position to * the HTML tag corresponding to the clicked element. */ function onDocumentClick(event) { var element = event.target; if (element && element.hasAttribute('data-brackets-id')) { MessageBroker.send({"tagId": element.getAttribute('data-brackets-id')}); } } window.document.addEventListener("click", onDocumentClick); }(this)); ================================================ FILE: src/LiveDevelopment/MultiBrowserImpl/transports/NodeSocketTransport.js ================================================ /* * Copyright (c) 2014 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ // This transport provides a WebSocket connection between Brackets and a live browser preview. // This is just a thin wrapper around the Node extension (NodeSocketTransportDomain) that actually // provides the WebSocket server and handles the communication. We also rely on an injected script in // the browser for the other end of the transport. define(function (require, exports, module) { "use strict"; var FileUtils = require("file/FileUtils"), EventDispatcher = require("utils/EventDispatcher"), NodeDomain = require("utils/NodeDomain"); // The script that will be injected into the previewed HTML to handle the other side of the socket connection. var NodeSocketTransportRemote = require("text!LiveDevelopment/MultiBrowserImpl/transports/remote/NodeSocketTransportRemote.js"); // The node extension that actually provides the WebSocket server. var domainPath = FileUtils.getNativeBracketsDirectoryPath() + "/" + FileUtils.getNativeModuleDirectoryPath(module) + "/node/NodeSocketTransportDomain"; var NodeSocketTransportDomain = new NodeDomain("nodeSocketTransport", domainPath); // This must match the port declared in NodeSocketTransportDomain.js. // TODO: randomize this? var SOCKET_PORT = 8123; /** * Returns the script that should be injected into the browser to handle the other end of the transport. * @return {string} */ function getRemoteScript() { return "\n"; } // Events // We can simply retrigger the events we receive from the node domain directly, since they're in // the same format expected by clients of the transport. ["connect", "message", "close"].forEach(function (type) { NodeSocketTransportDomain.on(type, function () { console.log("NodeSocketTransport - event - " + type + " - " + JSON.stringify(Array.prototype.slice.call(arguments, 1))); // Remove the event object from the argument list. exports.trigger(type, Array.prototype.slice.call(arguments, 1)); }); }); EventDispatcher.makeEventDispatcher(exports); // Exports exports.getRemoteScript = getRemoteScript; // Proxy the node domain methods directly through, since they have exactly the same // signatures as the ones we're supposed to provide. ["start", "send", "close"].forEach(function (method) { exports[method] = function () { var args = Array.prototype.slice.call(arguments); args.unshift(method); console.log("NodeSocketTransport - " + args); NodeSocketTransportDomain.exec.apply(NodeSocketTransportDomain, args); }; }); }); ================================================ FILE: src/LiveDevelopment/MultiBrowserImpl/transports/node/NodeSocketTransportDomain.js ================================================ /* * Copyright (c) 2014 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /*eslint-env node */ /*jslint node: true */ "use strict"; var WebSocketServer = require("ws").Server, _ = require("lodash"); /** * @private * The WebSocket server we listen for incoming connections on. * @type {?WebSocketServer} */ var _wsServer; /** * @private * The Brackets domain manager for registering node extensions. * @type {?DomainManager} */ var _domainManager; /** * @private * The ID that should be allocated to the next client that connects to the transport. * @type {number} */ var _nextClientId = 1; /** * @private * A map of client IDs to the URL and WebSocket for the given ID. * @type {Object.} */ var _clients = {}; // This must match the port declared in NodeSocketTransport.js. // TODO: randomize this? var SOCKET_PORT = 8123; /** * @private * Returns the client info for a given WebSocket, or null if that socket isn't registered. * @param {WebSocket} ws * @return {?{id: number, url: string, socket: WebSocket}} */ function _clientForSocket(ws) { return _.find(_clients, function (client) { return (client.socket === ws); }); } /** * @private * Creates the WebSocketServer and handles incoming connections. */ function _createServer() { if (!_wsServer) { // TODO: make port configurable, or use random port _wsServer = new WebSocketServer({port: SOCKET_PORT}); _wsServer.on("connection", function (ws) { ws.on("message", function (msg) { console.log("WebSocketServer - received - " + msg); var msgObj; try { msgObj = JSON.parse(msg); } catch (e) { console.error("nodeSocketTransport: Error parsing message: " + msg); return; } // See the comment in NodeSocketTransportRemote.connect() for why we have an extra // layer of transport-layer message objects surrounding the protocol messaging. if (msgObj.type === "connect") { if (!msgObj.url) { console.error("nodeSocketTransport: Malformed connect message: " + msg); return; } var clientId = _nextClientId++; _clients[clientId] = { id: clientId, url: msgObj.url, socket: ws }; console.log("emitting connect event"); _domainManager.emitEvent("nodeSocketTransport", "connect", [clientId, msgObj.url]); } else if (msgObj.type === "message") { var client = _clientForSocket(ws); if (client) { _domainManager.emitEvent("nodeSocketTransport", "message", [client.id, msgObj.message]); } else { console.error("nodeSocketTransport: Couldn't locate client for message: " + msg); } } else { console.error("nodeSocketTransport: Got bad socket message type: " + msg); } }).on("error", function (e) { // TODO: emit error event var client = _clientForSocket(ws); console.error("nodeSocketTransport: Error on socket for client " + JSON.stringify(client) + ": " + e); }).on("close", function () { var client = _clientForSocket(ws); if (client) { _domainManager.emitEvent("nodeSocketTransport", "close", [client.id]); delete _clients[client.id]; } else { console.error("nodeSocketTransport: Socket closed, but couldn't locate client"); } }); }).on("error", function (e) { // TODO: emit error event console.error("nodeSocketTransport: Error on live preview server creation: " + e); }); } } /** * Initializes the socket server. * @param {string} url */ function _cmdStart(url) { _createServer(); } /** * Sends a transport-layer message over the socket. * @param {number|Array.} idOrArray A client ID or array of client IDs to send the message to. * @param {string} msgStr The message to send as a JSON string. */ function _cmdSend(idOrArray, msgStr) { if (!Array.isArray(idOrArray)) { idOrArray = [idOrArray]; } idOrArray.forEach(function (id) { var client = _clients[id]; if (!client) { console.error("nodeSocketTransport: Couldn't find client ID: " + id); } else { client.socket.send(msgStr); } }); } /** * Closes the connection for a given client ID. * @param {number} clientId */ function _cmdClose(clientId) { var client = _clients[clientId]; if (client) { client.socket.close(); delete _clients[clientId]; } } /** * Initializes the domain and registers commands. * @param {DomainManager} domainManager The DomainManager for the server */ function init(domainManager) { _domainManager = domainManager; if (!domainManager.hasDomain("nodeSocketTransport")) { domainManager.registerDomain("nodeSocketTransport", {major: 0, minor: 1}); } domainManager.registerCommand( "nodeSocketTransport", // domain name "start", // command name _cmdStart, // command handler function false, // this command is synchronous in Node "Creates the WS server", [] ); domainManager.registerCommand( "nodeSocketTransport", // domain name "send", // command name _cmdSend, // command handler function false, // this command is synchronous in Node "Sends a message to a given client or list of clients", [ {name: "idOrArray", type: "number|Array.", description: "id or array of ids to send the message to"}, {name: "message", type: "string", description: "JSON message to send"} ], [] ); domainManager.registerCommand( "nodeSocketTransport", // domain name "close", // command name _cmdClose, // command handler function false, // this command is synchronous in Node "Closes the connection to a given client", [ {name: "id", type: "number", description: "id of connection to close"} ], [] ); domainManager.registerEvent( "nodeSocketTransport", "connect", [ {name: "clientID", type: "number", description: "ID of live preview page connecting to live development"}, {name: "url", type: "string", description: "URL of page that live preview is connecting from"} ] ); domainManager.registerEvent( "nodeSocketTransport", "message", [ {name: "clientID", type: "number", description: "ID of live preview page sending message"}, {name: "msg", type: "string", description: "JSON message from client page"} ] ); domainManager.registerEvent( "nodeSocketTransport", "close", [ {name: "clientID", type: "number", description: "ID of live preview page being closed"} ] ); } exports.init = init; ================================================ FILE: src/LiveDevelopment/MultiBrowserImpl/transports/remote/NodeSocketTransportRemote.js ================================================ /* * Copyright (c) 2014 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ // This is a transport injected into the browser via a script that handles the low // level communication between the live development protocol handlers on both sides. // This transport provides a web socket mechanism. It's injected separately from the // protocol handler so that the transport can be changed separately. (function (global) { "use strict"; var WebSocketTransport = { /** * @private * The WebSocket that we communicate with Brackets over. * @type {?WebSocket} */ _ws: null, /** * @private * An object that contains callbacks to handle various transport events. See `setCallbacks()`. * @type {?{connect: ?function, message: ?function(string), close: ?function}} */ _callbacks: null, /** * Sets the callbacks that should be called when various transport events occur. All callbacks * are optional, but you should at least implement "message" or nothing interesting will happen :) * @param {?{connect: ?function, message: ?function(string), close: ?function}} callbacks * The callbacks to set. * connect - called when a connection is established to Brackets * message(msgStr) - called with a string message sent from Brackets * close - called when Brackets closes the connection */ setCallbacks: function (callbacks) { if (!global._Brackets_LiveDev_Socket_Transport_URL) { console.error("[Brackets LiveDev] No socket transport URL injected"); } else { this._callbacks = callbacks; } }, /** * Connects to the NodeSocketTransport in Brackets at the given WebSocket URL. * @param {string} url */ connect: function (url) { var self = this; this._ws = new WebSocket(url); // One potential source of confusion: the transport sends two "types" of messages - // these are distinct from the protocol's own messages. This is because this transport // needs to send an initial "connect" message telling the Brackets side of the transport // the URL of the page that it's connecting from, distinct from the actual protocol // message traffic. Actual protocol messages are sent as a JSON payload in a message of // type "message". // // Other transports might not need to do this - for example, a transport that simply // talks to an iframe within the same process already knows what URL that iframe is // pointing to, so the only comunication that needs to happen via postMessage() is the // actual protocol message strings, and no extra wrapping is necessary. this._ws.onopen = function (event) { // Send the initial "connect" message to tell the other end what URL we're from. self._ws.send(JSON.stringify({ type: "connect", url: global.location.href })); console.log("[Brackets LiveDev] Connected to Brackets at " + url); if (self._callbacks && self._callbacks.connect) { self._callbacks.connect(); } }; this._ws.onmessage = function (event) { console.log("[Brackets LiveDev] Got message: " + event.data); if (self._callbacks && self._callbacks.message) { self._callbacks.message(event.data); } }; this._ws.onclose = function (event) { self._ws = null; if (self._callbacks && self._callbacks.close) { self._callbacks.close(); } }; // TODO: onerror }, /** * Sends a message over the transport. * @param {string} msgStr The message to send. */ send: function (msgStr) { if (this._ws) { // See comment in `connect()` above about why we wrap the message in a transport message // object. this._ws.send(JSON.stringify({ type: "message", message: msgStr })); } else { console.log("[Brackets LiveDev] Tried to send message over closed connection: " + msgStr); } }, /** * Establish web socket connection. */ enable: function () { this.connect(global._Brackets_LiveDev_Socket_Transport_URL); } }; global._Brackets_LiveDev_Transport = WebSocketTransport; }(this)); ================================================ FILE: src/LiveDevelopment/Servers/BaseServer.js ================================================ /* * Copyright (c) 2013 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /*jslint regexp: true */ define(function (require, exports, module) { "use strict"; /** * Base class for live preview servers * * Configuration parameters for this server: * - baseUrl - Optional base URL (populated by the current project) * - pathResolver - Function to covert absolute native paths to project relative paths * - root - Native path to the project root (and base URL) * * @constructor * @param {!{baseUrl: string, root: string, pathResolver: function(string): string}} config */ function BaseServer(config) { this._baseUrl = config.baseUrl; this._root = config.root; // ProjectManager.getProjectRoot().fullPath this._pathResolver = config.pathResolver; // ProjectManager.makeProjectRelativeIfPossible(doc.file.fullPath) this._liveDocuments = {}; } /** * Returns a base url for current project. * * @return {string} * Base url for current project. */ BaseServer.prototype.getBaseUrl = function () { return this._baseUrl; }; /** * @private * Augments the given Brackets document with information that's useful for live development * @param {Object} liveDocument */ BaseServer.prototype._setDocInfo = function (liveDocument) { var parentUrl, matches, doc = liveDocument.doc; // FUTURE: some of these things should just be moved into core Document; others should // be in a LiveDevelopment-specific object attached to the doc. matches = /^(.*\/)(.+\.([^.]+))$/.exec(doc.file.fullPath); if (!matches) { return; } doc.extension = matches[3]; parentUrl = this.pathToUrl(matches[1]); doc.url = parentUrl + encodeURI(matches[2]); // the root represents the document that should be displayed in the browser // for live development (the file for HTML files) // TODO: Issue #2033 Improve how default page is determined doc.root = { url: doc.url }; // TODO: Better workflow of liveDocument.doc.url assignment // Force sync the browser after a URL is assigned if (doc.isDirty && liveDocument._updateBrowser) { liveDocument._updateBrowser(); } }; /** * Returns a URL for a given path * @param {string} path Absolute path to covert to a URL * @return {?string} Converts a path within the project root to a URL. * Returns null if the path is not a descendant of the project root. */ BaseServer.prototype.pathToUrl = function (path) { var baseUrl = this.getBaseUrl(), relativePath = this._pathResolver(path); // See if base url has been specified and path is within project if (relativePath !== path) { // Map to server url. Base url is already encoded, so don't encode again. var encodedDocPath = encodeURI(path); var encodedProjectPath = encodeURI(this._root); return encodedDocPath.replace(encodedProjectPath, baseUrl); } return null; }; /** * Convert a URL to a local full file path * @param {string} url * @return {?string} The absolute path for given URL or null if the path is * not a descendant of the project. */ BaseServer.prototype.urlToPath = function (url) { var path, baseUrl = ""; baseUrl = this.getBaseUrl(); if (baseUrl !== "" && url.indexOf(baseUrl) === 0) { // Use base url to translate to local file path. // Need to use encoded project path because it's decoded below. path = url.replace(baseUrl, encodeURI(this._root)); return decodeURI(path); } return null; }; /** * Called by LiveDevelopment before to prepare the server before navigating * to the project's base URL. The provider returns a jQuery promise. * The Live Development launch process waits until the promise * is resolved or rejected. If the promise is rejected, an error window * is shown and Live Development does not start.. * * @return {jQuery.Promise} Promise that may be asynchronously resolved * when the server is ready to handle HTTP requests. */ BaseServer.prototype.readyToServe = function () { // Base implementation always resolves return $.Deferred().resolve().promise(); }; /** * Determines if this server can serve local file. LiveDevServerManager * calls this method when determining if a server can serve a file. * @param {string} localPath A local path to file being served. * @return {boolean} true When the file can be served, otherwise false. */ BaseServer.prototype.canServe = function (localPath) { return true; }; BaseServer.prototype._documentKey = function (absolutePath) { return "/" + encodeURI(this._pathResolver(absolutePath)); }; /** * Adds a live document to server * @param {Object} liveDocument */ BaseServer.prototype.add = function (liveDocument) { if (!liveDocument) { return; } // use the project relative path as a key to lookup requests var key = this._documentKey(liveDocument.doc.file.fullPath); this._setDocInfo(liveDocument); this._liveDocuments[key] = liveDocument; }; /** * Removes a live document from the server * @param {Object} liveDocument */ BaseServer.prototype.remove = function (liveDocument) { if (!liveDocument) { return; } var key = this._liveDocuments[this._documentKey(liveDocument.doc.file.fullPath)]; if (key) { delete this._liveDocuments[key]; } }; /** * Lookup a live document using it's full path key * @param {string} path Absolute path to covert to a URL * @param {?Object} liveDocument Returns a live document or undefined if a * document does not exist for the path. */ BaseServer.prototype.get = function (path) { return this._liveDocuments[this._documentKey(path)]; }; /** * Clears all live documents currently attached to the server */ BaseServer.prototype.clear = function () { this._liveDocuments = {}; }; /** * Start the server */ BaseServer.prototype.start = function () { // do nothing }; /** * Stop the server */ BaseServer.prototype.stop = function () { // do nothing }; exports.BaseServer = BaseServer; }); ================================================ FILE: src/LiveDevelopment/Servers/FileServer.js ================================================ /* * Copyright (c) 2013 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ define(function (require, exports, module) { "use strict"; var BaseServer = require("LiveDevelopment/Servers/BaseServer").BaseServer, LiveDevelopmentUtils = require("LiveDevelopment/LiveDevelopmentUtils"); // The path on Windows starts with a drive letter (e.g. "C:"). // In order to make it a valid file: URL we need to add an // additional slash to the prefix. var PREFIX = (brackets.platform === "win") ? "file:///" : "file://"; /** * Server for file: URLs * * Configuration parameters for this server: * - baseUrl - Optional base URL (populated by the current project) * - pathResolver - Function to covert absolute native paths to project relative paths * - root - Native path to the project root (and base URL) * * @constructor * @param {!{baseUrl: string, root: string, pathResolver: function(string): string}} config * @extends {BaseServer} */ function FileServer(config) { BaseServer.call(this, config); } FileServer.prototype = Object.create(BaseServer.prototype); FileServer.prototype.constructor = FileServer; /** * Determines whether we can serve local file. * @param {string} localPath A local path to file being served. * @return {boolean} true for yes, otherwise false. */ FileServer.prototype.canServe = function (localPath) { // FileServer requires that the base URL is undefined and static HTML files return (!this._baseUrl && LiveDevelopmentUtils.isStaticHtmlFileExt(localPath)); }; /** * Convert a file: URL to a absolute file path * @param {string} url * @return {?string} The absolute path for given file: URL or null if the path is * not a descendant of the project. */ FileServer.prototype.urlToPath = function (url) { if (url.indexOf(PREFIX) === 0) { // Convert a file URL to local file path return decodeURI(url.slice(PREFIX.length)); } return null; }; /** * Returns a file: URL for a given absolute path * @param {string} path Absolute path to covert to a file: URL * @return {string} Converts an absolute path within the project root to a file: URL. */ FileServer.prototype.pathToUrl = function (path) { return encodeURI(PREFIX + path); }; exports.FileServer = FileServer; }); ================================================ FILE: src/LiveDevelopment/Servers/UserServer.js ================================================ /* * Copyright (c) 2013 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ define(function (require, exports, module) { "use strict"; var BaseServer = require("LiveDevelopment/Servers/BaseServer").BaseServer, LiveDevelopmentUtils = require("LiveDevelopment/LiveDevelopmentUtils"); /** * Live preview server for user specified server as defined with Live Preview Base Url * Project setting. In a clean installation of Brackets, this is the highest priority * server provider, if defined. * * Configuration parameters for this server: * - baseUrl - Optional base URL (populated by the current project) * - pathResolver - Function to covert absolute native paths to project relative paths * - root - Native path to the project root (and base URL) * * @constructor * @param {!{baseUrl: string, root: string, pathResolver: function(string)}} config * @extends {BaseServer} */ function UserServer(config) { BaseServer.call(this, config); } UserServer.prototype = Object.create(BaseServer.prototype); UserServer.prototype.constructor = UserServer; /** * Determines whether we can serve local file. * @param {string} localPath A local path to file being served. * @return {boolean} true for yes, otherwise false. */ UserServer.prototype.canServe = function (localPath) { // UserServer can only function when the project specifies a base URL if (!this._baseUrl) { return false; } // If we can't transform the local path to a project relative path, // the path cannot be served if (localPath === this._pathResolver(localPath)) { return false; } return LiveDevelopmentUtils.isStaticHtmlFileExt(localPath) || LiveDevelopmentUtils.isServerHtmlFileExt(localPath); }; exports.UserServer = UserServer; }); ================================================ FILE: src/LiveDevelopment/launch.html ================================================ Waiting...
    ================================================ FILE: src/LiveDevelopment/main.js ================================================ /* * Copyright (c) 2012 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /*global less */ /** * main integrates LiveDevelopment into Brackets * * This module creates two menu items: * * "Go Live": open or close a Live Development session and visualize the status * "Highlight": toggle source highlighting */ define(function main(require, exports, module) { "use strict"; var DocumentManager = require("document/DocumentManager"), Commands = require("command/Commands"), AppInit = require("utils/AppInit"), LiveDevelopment = require("LiveDevelopment/LiveDevelopment"), MultiBrowserLiveDev = require("LiveDevelopment/LiveDevMultiBrowser"), Inspector = require("LiveDevelopment/Inspector/Inspector"), CommandManager = require("command/CommandManager"), PreferencesManager = require("preferences/PreferencesManager"), Dialogs = require("widgets/Dialogs"), DefaultDialogs = require("widgets/DefaultDialogs"), UrlParams = require("utils/UrlParams").UrlParams, Strings = require("strings"), ExtensionUtils = require("utils/ExtensionUtils"), StringUtils = require("utils/StringUtils"); var params = new UrlParams(); var config = { experimental: false, // enable experimental features debug: true, // enable debug output and helpers autoconnect: false, // go live automatically after startup? highlight: true, // enable highlighting? highlightConfig: { // the highlight configuration for the Inspector borderColor: {r: 255, g: 229, b: 153, a: 0.66}, contentColor: {r: 111, g: 168, b: 220, a: 0.55}, marginColor: {r: 246, g: 178, b: 107, a: 0.66}, paddingColor: {r: 147, g: 196, b: 125, a: 0.66}, showInfo: true } }; // Status labels/styles are ordered: error, not connected, progress1, progress2, connected. var _status, _allStatusStyles = ["warning", "info", "success", "out-of-sync", "sync-error"].join(" "); var _$btnGoLive; // reference to the GoLive button // current selected implementation (LiveDevelopment | LiveDevMultiBrowser) var LiveDevImpl; // "livedev.multibrowser" preference var PREF_MULTIBROWSER = "multibrowser"; var prefs = PreferencesManager.getExtensionPrefs("livedev"); var multiBrowserPref = prefs.definePreference(PREF_MULTIBROWSER, "boolean", false, { description: Strings.DESCRIPTION_LIVE_DEV_MULTIBROWSER }); // "livedev.remoteHighlight" preference var PREF_REMOTEHIGHLIGHT = "remoteHighlight"; var remoteHighlightPref = prefs.definePreference(PREF_REMOTEHIGHLIGHT, "object", { animateStartValue: { "background-color": "rgba(0, 162, 255, 0.5)", "opacity": 0 }, animateEndValue: { "background-color": "rgba(0, 162, 255, 0)", "opacity": 0.6 }, "paddingStyling": { "border-width": "1px", "border-style": "dashed", "border-color": "rgba(0, 162, 255, 0.5)" }, "marginStyling": { "background-color": "rgba(21, 165, 255, 0.58)" }, "borderColor": "rgba(21, 165, 255, 0.85)", "showPaddingMargin": true }, { description: Strings.DESCRIPTION_LIVE_DEV_HIGHLIGHT_SETTINGS }); /** Toggles or sets the preference **/ function _togglePref(key, value) { var val, oldPref = !!prefs.get(key); if (value === undefined) { val = !oldPref; } else { val = !!value; } // update menu if (val !== oldPref) { prefs.set(key, val); } return val; } /* Toggles or sets the "livedev.multibrowser" preference */ function _toggleLivePreviewMultiBrowser(value) { var val = _togglePref(PREF_MULTIBROWSER, value); CommandManager.get(Commands.TOGGLE_LIVE_PREVIEW_MB_MODE).setChecked(val); // Issue #10217: multi-browser does not support user server, so disable // the setting if it is enabled. CommandManager.get(Commands.FILE_PROJECT_SETTINGS).setEnabled(!val); } /** Load Live Development LESS Style */ function _loadStyles() { var lessText = require("text!LiveDevelopment/main.less"); less.render(lessText, function onParse(err, tree) { console.assert(!err, err); ExtensionUtils.addEmbeddedStyleSheet(tree.css); }); } /** * Change the appearance of a button. Omit text to remove any extra text; omit style to return to default styling; * omit tooltip to leave tooltip unchanged. */ function _setLabel($btn, text, style, tooltip) { // Clear text/styles from previous status $("span", $btn).remove(); $btn.removeClass(_allStatusStyles); // Set text/styles for new status if (text && text.length > 0) { $("") .addClass(style) .text(text) .appendTo($btn); } else { $btn.addClass(style); } if (tooltip) { $btn.attr("title", tooltip); } } /** * Toggles LiveDevelopment and synchronizes the state of UI elements that reports LiveDevelopment status * * Stop Live Dev when in an active state (ACTIVE, OUT_OF_SYNC, SYNC_ERROR). * Start Live Dev when in an inactive state (ERROR, INACTIVE). * Do nothing when in a connecting state (CONNECTING, LOADING_AGENTS). */ function _handleGoLiveCommand() { if (LiveDevImpl.status >= LiveDevImpl.STATUS_ACTIVE) { LiveDevImpl.close(); } else if (LiveDevImpl.status <= LiveDevImpl.STATUS_INACTIVE) { if (!params.get("skipLiveDevelopmentInfo") && !PreferencesManager.getViewState("livedev.afterFirstLaunch")) { PreferencesManager.setViewState("livedev.afterFirstLaunch", "true"); Dialogs.showModalDialog( DefaultDialogs.DIALOG_ID_INFO, Strings.LIVE_DEVELOPMENT_INFO_TITLE, Strings.LIVE_DEVELOPMENT_INFO_MESSAGE ).done(function (id) { LiveDevImpl.open(); }); } else { LiveDevImpl.open(); } } } /** Called on status change */ function _showStatusChangeReason(reason) { // Destroy the previous twipsy (options are not updated otherwise) _$btnGoLive.twipsy("hide").removeData("twipsy"); // If there was no reason or the action was an explicit request by the user, don't show a twipsy if (!reason || reason === "explicit_close") { return; } // Translate the reason var translatedReason = Strings["LIVE_DEV_" + reason.toUpperCase()]; if (!translatedReason) { translatedReason = StringUtils.format(Strings.LIVE_DEV_CLOSED_UNKNOWN_REASON, reason); } // Configure the twipsy var options = { placement: "left", trigger: "manual", autoHideDelay: 5000, title: function () { return translatedReason; } }; // Show the twipsy with the explanation _$btnGoLive.twipsy(options).twipsy("show"); } /** Create the menu item "Go Live" */ function _setupGoLiveButton() { if (!_$btnGoLive) { _$btnGoLive = $("#toolbar-go-live"); _$btnGoLive.click(function onGoLive() { _handleGoLiveCommand(); }); } LiveDevImpl.on("statusChange", function statusChange(event, status, reason) { // status starts at -1 (error), so add one when looking up name and style // See the comments at the top of LiveDevelopment.js for details on the // various status codes. _setLabel(_$btnGoLive, null, _status[status + 1].style, _status[status + 1].tooltip); _showStatusChangeReason(reason); if (config.autoconnect) { window.sessionStorage.setItem("live.enabled", status === 3); } }); // Initialize tooltip for 'not connected' state _setLabel(_$btnGoLive, null, _status[1].style, _status[1].tooltip); } /** Maintains state of the Live Preview menu item */ function _setupGoLiveMenu() { LiveDevImpl.on("statusChange", function statusChange(event, status) { // Update the checkmark next to 'Live Preview' menu item // Add checkmark when status is STATUS_ACTIVE; otherwise remove it CommandManager.get(Commands.FILE_LIVE_FILE_PREVIEW).setChecked(status === LiveDevImpl.STATUS_ACTIVE); CommandManager.get(Commands.FILE_LIVE_HIGHLIGHT).setEnabled(status === LiveDevImpl.STATUS_ACTIVE); }); } function _updateHighlightCheckmark() { CommandManager.get(Commands.FILE_LIVE_HIGHLIGHT).setChecked(config.highlight); } function _handlePreviewHighlightCommand() { config.highlight = !config.highlight; _updateHighlightCheckmark(); if (config.highlight) { LiveDevImpl.showHighlight(); } else { LiveDevImpl.hideHighlight(); } PreferencesManager.setViewState("livedev.highlight", config.highlight); } /** * Sets the MultiBrowserLiveDev implementation if multibrowser is truthy, * keeps default LiveDevelopment implementation based on CDT otherwise. * It also resets the listeners and UI elements. */ function _setImplementation(multibrowser) { if (multibrowser) { // set implemenation LiveDevImpl = MultiBrowserLiveDev; // update styles for UI status _status = [ { tooltip: Strings.LIVE_DEV_STATUS_TIP_NOT_CONNECTED, style: "warning" }, { tooltip: Strings.LIVE_DEV_STATUS_TIP_NOT_CONNECTED, style: "" }, { tooltip: Strings.LIVE_DEV_STATUS_TIP_PROGRESS1, style: "info" }, { tooltip: Strings.LIVE_DEV_STATUS_TIP_CONNECTED, style: "success" }, { tooltip: Strings.LIVE_DEV_STATUS_TIP_OUT_OF_SYNC, style: "out-of-sync" }, { tooltip: Strings.LIVE_DEV_STATUS_TIP_SYNC_ERROR, style: "sync-error" }, { tooltip: Strings.LIVE_DEV_STATUS_TIP_PROGRESS1, style: "info" }, { tooltip: Strings.LIVE_DEV_STATUS_TIP_PROGRESS1, style: "info" } ]; } else { LiveDevImpl = LiveDevelopment; _status = [ { tooltip: Strings.LIVE_DEV_STATUS_TIP_NOT_CONNECTED, style: "warning" }, { tooltip: Strings.LIVE_DEV_STATUS_TIP_NOT_CONNECTED, style: "" }, { tooltip: Strings.LIVE_DEV_STATUS_TIP_PROGRESS1, style: "info" }, { tooltip: Strings.LIVE_DEV_STATUS_TIP_PROGRESS2, style: "info" }, { tooltip: Strings.LIVE_DEV_STATUS_TIP_CONNECTED, style: "success" }, { tooltip: Strings.LIVE_DEV_STATUS_TIP_OUT_OF_SYNC, style: "out-of-sync" }, { tooltip: Strings.LIVE_DEV_STATUS_TIP_SYNC_ERROR, style: "sync-error" } ]; } // setup status changes listeners for new implementation _setupGoLiveButton(); _setupGoLiveMenu(); // toggle the menu _toggleLivePreviewMultiBrowser(multibrowser); } /** Setup window references to useful LiveDevelopment modules */ function _setupDebugHelpers() { window.ld = LiveDevelopment; window.i = Inspector; window.report = function report(params) { window.params = params; console.info(params); }; } /** force reload the live preview */ function _handleReloadLivePreviewCommand() { if (LiveDevelopment.status >= LiveDevelopment.STATUS_ACTIVE) { LiveDevelopment.reload(); } } /** Initialize LiveDevelopment */ AppInit.appReady(function () { params.parse(); config.remoteHighlight = prefs.get(PREF_REMOTEHIGHLIGHT); Inspector.init(config); LiveDevelopment.init(config); // init experimental multi-browser implementation // it can be enable by setting 'livedev.multibrowser' preference to true. // It has to be initiated at this point in case of dynamically switching // by changing the preference value. MultiBrowserLiveDev.init(config); _loadStyles(); _updateHighlightCheckmark(); _setImplementation(prefs.get(PREF_MULTIBROWSER)); if (config.debug) { _setupDebugHelpers(); } // trigger autoconnect if (config.autoconnect && window.sessionStorage.getItem("live.enabled") === "true" && DocumentManager.getCurrentDocument()) { _handleGoLiveCommand(); } // Redraw highlights when window gets focus. This ensures that the highlights // will be in sync with any DOM changes that may have occurred. $(window).focus(function () { if (Inspector.connected() && config.highlight) { LiveDevelopment.redrawHighlight(); } }); multiBrowserPref .on("change", function () { // Stop the current session if it is open and set implementation based on // the pref value. We could start the new implementation immediately, but // since the current document is potentially a user preferences file, Live // Preview will not locate the html file to serve. if (LiveDevImpl && LiveDevImpl.status >= LiveDevImpl.STATUS_ACTIVE) { LiveDevImpl.close() .done(function () { // status changes will now be listened by the new implementation LiveDevImpl.off("statusChange"); _setImplementation(prefs.get(PREF_MULTIBROWSER)); }); } else { _setImplementation(prefs.get(PREF_MULTIBROWSER)); } }); remoteHighlightPref .on("change", function () { config.remoteHighlight = prefs.get(PREF_REMOTEHIGHLIGHT); if (LiveDevImpl && LiveDevImpl.status >= LiveDevImpl.STATUS_ACTIVE) { LiveDevImpl.agents.remote.call("updateConfig",JSON.stringify(config)); } }); }); // init prefs PreferencesManager.stateManager.definePreference("livedev.highlight", "boolean", true) .on("change", function () { config.highlight = PreferencesManager.getViewState("livedev.highlight"); _updateHighlightCheckmark(); }); config.highlight = PreferencesManager.getViewState("livedev.highlight"); // init commands CommandManager.register(Strings.CMD_LIVE_FILE_PREVIEW, Commands.FILE_LIVE_FILE_PREVIEW, _handleGoLiveCommand); CommandManager.register(Strings.CMD_LIVE_HIGHLIGHT, Commands.FILE_LIVE_HIGHLIGHT, _handlePreviewHighlightCommand); CommandManager.register(Strings.CMD_RELOAD_LIVE_PREVIEW, Commands.CMD_RELOAD_LIVE_PREVIEW, _handleReloadLivePreviewCommand); CommandManager.register(Strings.CMD_TOGGLE_LIVE_PREVIEW_MB_MODE, Commands.TOGGLE_LIVE_PREVIEW_MB_MODE, _toggleLivePreviewMultiBrowser); CommandManager.get(Commands.FILE_LIVE_HIGHLIGHT).setEnabled(false); // Export public functions }); ================================================ FILE: src/LiveDevelopment/main.less ================================================ /* * Copyright (c) 2012 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ .CodeMirror { .highlight { background: #ecc; } .flash { -webkit-animation: flash 1s; } } @-webkit-keyframes flash { from { background: #ecc; } to { background: inherit; } } ================================================ FILE: src/LiveDevelopment/transports/WebSocketTransport.js ================================================ /* * Copyright (c) 2017 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /** * This transport provides a WebSocket connection between Brackets and a live browser preview. * This is just a thin wrapper around the Node extension (WebSocketTransportDomain) that actually * provides the WebSocket server and handles the communication. We also rely on an injected script in * the browser for the other end of the transport. */ define(function (require, exports, module) { "use strict"; var FileUtils = require("file/FileUtils"), NodeDomain = require("utils/NodeDomain"), EditorManager = require("editor/EditorManager"), HTMLInstrumentation = require("language/HTMLInstrumentation"); // The node extension that actually provides the WebSocket server. var domainPath = FileUtils.getNativeBracketsDirectoryPath() + "/" + FileUtils.getNativeModuleDirectoryPath(module) + "/node/WebSocketTransportDomain"; var WebSocketTransportDomain = new NodeDomain("webSocketTransport", domainPath); // Events WebSocketTransportDomain.on("message", function (obj, message) { console.log("WebSocketTransport - event - message" + " - " + message); var editor = EditorManager.getActiveEditor(), position = HTMLInstrumentation.getPositionFromTagId(editor, parseInt(message, 10)); if (position) { editor.setCursorPos(position.line, position.ch, true); } }); function createWebSocketServer(port) { WebSocketTransportDomain.exec("start", parseInt(port, 10)); } function closeWebSocketServer() { WebSocketTransportDomain.exec("close"); } exports.createWebSocketServer = createWebSocketServer; exports.closeWebSocketServer = closeWebSocketServer; }); ================================================ FILE: src/LiveDevelopment/transports/node/WebSocketTransportDomain.js ================================================ /* * Copyright (c) 2017 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /** * WebSocketTransportDomain creates a websocket server for Live Preview * It receives the message containing tagID from the Remote Client(onClick) * and emits an event which is listened by WebSocketTransport which * brings the cursor to the tag corresponding to that particular tagID */ /*eslint-env node */ /*jslint node: true */ "use strict"; var WebSocketServer = require("ws").Server; /** * @private * The WebSocket server we listen for incoming connections on. * @type {?WebSocketServer} */ var _wsServer; /** * @private * The Brackets domain manager for registering node extensions. * @type {?DomainManager} */ var _domainManager; /** * @private * Creates the WebSocketServer and handles incoming connections. */ function _createServer(socketPort) { if (!_wsServer) { // TODO: make port configurable, or use random port _wsServer = new WebSocketServer({port: socketPort}); _wsServer.on("connection", function (ws) { ws.on("message", function (msg) { console.log("WebSocketServer - received - " + msg); var msgObj; try { msgObj = JSON.parse(msg); } catch (e) { console.error("webSocketTransport: Error parsing message: " + msg); return; } if (msgObj.type === "message") { _domainManager.emitEvent("webSocketTransport", "message", msgObj.message); } else { console.error("webSocketTransport: Got bad socket message type: " + msg); } }).on("error", function (e) { console.error("webSocketTransport: Error on socket : " + e); }).on("close", function () { console.log("webSocketTransport closed"); }); }).on("error", function (e) { console.error("webSocketTransport: Error on live preview server creation: " + e); }); } } /** * Initializes the socket server. * @param {number} port */ function _cmdStart(port) { _createServer(port); } /** * Kill the WebSocketServer */ function _cmdClose() { if (_wsServer) { _wsServer.close(); _wsServer = null; } } /** * Initializes the domain and registers commands. * @param {DomainManager} domainManager The DomainManager for the server */ function init(domainManager) { _domainManager = domainManager; if (!domainManager.hasDomain("webSocketTransport")) { domainManager.registerDomain("webSocketTransport", {major: 0, minor: 1}); } domainManager.registerEvent( "webSocketTransport", "message", [ { name: "msg", type: "string", description: "JSON message from client page" } ] ); domainManager.registerCommand( "webSocketTransport", // domain name "start", // command name _cmdStart, // command handler function false, // this command is synchronous in Node "Creates the WS server", [ { name: "port", type: "number", description: "Port on which server needs to listen" } ], [] ); domainManager.registerCommand( "webSocketTransport", // domain name "close", // command name _cmdClose, // command handler function false, // this command is synchronous in Node "Kills the websocket server", [] ); } exports.init = init; ================================================ FILE: src/base-config/keyboard.json ================================================ { "file.newDoc": [ "Ctrl-N" ], "file.open": [ "Ctrl-O" ], "file.close": [ "Ctrl-W" ], "file.openFolder": [ "Ctrl-Alt-O" ], "file.close_all": [ "Ctrl-Shift-W" ], "file.save": [ "Ctrl-S" ], "file.saveAll": [ "Ctrl-Alt-S" ], "file.saveAs": [ "Ctrl-Shift-S" ], "file.liveFilePreview": [ "Ctrl-Alt-P" ], "file.reloadLivePreview": [ { "key": "Ctrl-Shift-R" } ], "file.previewHighlight": [ "Ctrl-Shift-C" ], "file.quit": [ "Ctrl-Q" ], "edit.undo": [ "Ctrl-Z" ], "edit.redo": [ { "key": "Ctrl-Y" }, { "key": "Cmd-Shift-Z", "platform": "mac" } ], "edit.cut": [ "Ctrl-X" ], "edit.copy": [ "Ctrl-C" ], "edit.paste": [ "Ctrl-V" ], "edit.selectAll": [ "Ctrl-A" ], "edit.selectLine": [ { "key": "Ctrl-L" }, { "key": "Ctrl-L", "platform": "mac" } ], "edit.splitSelIntoLines": [ { "key": "Ctrl-Alt-L" }, { "key":"Ctrl-Shift-L", "platform": "linux" } ], "edit.addCursorToPrevLine": [ { "key": "Shift-Alt-Up", "displayKey": "Shift-Alt-↑" } ], "edit.addCursorToNextLine": [ { "key": "Shift-Alt-Down", "displayKey": "Shift-Alt-↓" } ], "edit.indent": [ { "key": "Ctrl-]" } ], "edit.unindent": [ { "key": "Ctrl-[" } ], "edit.duplicate": [ "Ctrl-D" ], "edit.deletelines": [ "Ctrl-Shift-D" ], "edit.lineUp": [ { "key": "Ctrl-Shift-Up", "displayKey": "Ctrl-Shift-↑" }, { "key": "Cmd-Ctrl-Up", "displayKey": "Cmd-Ctrl-↑", "platform": "mac" } ], "edit.lineDown": [ { "key": "Ctrl-Shift-Down", "displayKey": "Ctrl-Shift-↓" }, { "key": "Cmd-Ctrl-Down", "displayKey": "Cmd-Ctrl-↓", "platform": "mac" } ], "edit.openLineAbove": [ "Ctrl-Shift-Enter" ], "edit.openLineBelow": [ "Ctrl-Enter" ], "edit.lineComment": [ "Ctrl-/" ], "edit.blockComment": [ { "key": "Ctrl-Shift-/" }, { "key": "Cmd-Opt-/", "platform": "mac" } ], "edit.showCodeHints": [ { "key": "Ctrl-Space" }, { "key": "Ctrl-Space", "platform": "mac" } ], "cmd.find": [ "Ctrl-F" ], "cmd.findInFiles": [ "Ctrl-Shift-F" ], "cmd.findAllReferences": [ "Shift-F12" ], "cmd.replaceInFiles": [ { "key": "Ctrl-Shift-H" }, { "key": "Cmd-Opt-Shift-F", "platform": "mac" } ], "cmd.findNext": [ { "key": "F3" }, { "key": "Cmd-G", "platform": "mac" } ], "cmd.findPrevious": [ { "key": "Shift-F3" }, { "key": "Cmd-Shift-G", "platform": "mac" } ], "cmd.findAllAndSelect": [ { "key": "Alt-F3" }, { "key": "Cmd-Ctrl-G", "platform": "mac" } ], "cmd.addNextMatch": [ { "key": "Ctrl-B" } ], "cmd.skipCurrentMatch": [ { "key": "Ctrl-Shift-B" } ], "cmd.replace": [ { "key": "Ctrl-H" }, { "key": "Cmd-Opt-F", "platform": "mac" } ], "view.toggleSidebar": [ { "key" : "Ctrl-Alt-H" }, { "key": "Cmd-Shift-H", "platform": "mac" } ], "view.increaseFontSize": [ { "key": "Ctrl-=", "displayKey": "Ctrl-+" }, { "key": "Ctrl-+" } ], "view.decreaseFontSize": [ { "key": "Ctrl--", "displayKey": "Ctrl-−" } ], "view.restoreFontSize": [ "Ctrl-0" ], "view.scrollLineUp": [ { "key": "Ctrl-Up", "displayKey": "Ctrl-\u2191" }, { "key": "Ctrl-Opt-Up", "displayKey": "Ctrl-Opt-\u2191", "platform": "mac" } ], "view.scrollLineDown": [ { "key": "Ctrl-Down", "displayKey": "Ctrl-\u2193" }, { "key": "Ctrl-Opt-Down", "displayKey": "Ctrl-Opt-\u2193", "platform": "mac" } ], "navigate.quickOpen": [ "Ctrl-Shift-O" ], "navigate.gotoLine": [ { "key": "Ctrl-G" }, { "key": "Cmd-L", "platform": "mac" } ], "navigate.gotoDefinition": [ "Ctrl-T" ], "navigate.gotoDefinitionInProject": [ "Ctrl-Shift-T" ], "navigate.jumptoDefinition": [ "Ctrl-J" ], "navigate.gotoFirstProblem": [ { "key": "F8" }, { "key": "Cmd-'", "platform": "mac" } ], "navigate.nextDocListOrder": [ { "key": "Ctrl-PageDown" } ], "navigate.prevDocListOrder": [ { "key": "Ctrl-PageUp" } ], "navigate.toggleQuickEdit": [ "Ctrl-E" ], "navigate.toggleQuickDocs": [ "Ctrl-K" ], "navigate.previousMatch": [ { "key": "Alt-Up", "displayKey": "Alt-↑" } ], "navigate.nextMatch": [ { "key": "Alt-Down", "displayKey": "Alt-↓" } ], "navigate.newRule": [ "Ctrl-Alt-N" ], "file.rename": [ "F2" ], "help.support": [ "F1" ] } ================================================ FILE: src/brackets.config.dev.json ================================================ { "healthDataServerURL" : "https://healthdev.brackets.io/healthDataLog", "analyticsDataServerURL" : "https://cc-api-data-stage.adobe.io/ingest", "serviceKey" : "brackets-service", "environment" : "stage", "update_info_url" : "https://s3.amazonaws.com/files.brackets.io/updates/prerelease/.json", "notification_info_url" : "https://s3.amazonaws.com/files.brackets.io/notifications/prerelease/.json", "buildtype" : "dev" } ================================================ FILE: src/brackets.config.dist.json ================================================ { "healthDataServerURL" : "https://health.brackets.io/healthDataLog", "analyticsDataServerURL" : "https://cc-api-data.adobe.io/ingest", "serviceKey" : "brackets-service", "environment" : "production", "update_info_url" : "https://getupdates.brackets.io/getupdates?locale=", "notification_info_url" : "https://getupdates.brackets.io/getnotifications?locale=", "buildtype" : "production" } ================================================ FILE: src/brackets.config.json ================================================ { "config" : { "app_title" : "Brackets", "app_name_about" : "Brackets", "about_icon" : "styles/images/brackets_icon.svg", "how_to_use_url" : "https://github.com/adobe/brackets/wiki/How-to-Use-Brackets", "support_url" : "https://github.com/adobe/brackets/wiki/Troubleshooting", "suggest_feature_url" : "https://github.com/adobe/brackets/wiki/Suggest-a-Feature", "get_involved_url" : "https://github.com/adobe/brackets/blob/master/CONTRIBUTING.md", "glob_help_url" : "https://github.com/adobe/brackets/wiki/Using-File-Filters", "release_notes_url" : "https://github.com/adobe/brackets/wiki/Release-Notes", "homepage_url" : "http://brackets.io", "twitter_url" : "https://twitter.com/brackets", "troubleshoot_url" : "https://github.com/adobe/brackets/wiki/Troubleshooting#wiki-livedev", "twitter_name" : "@brackets", "contributors_url" : "https://api.github.com/repos/adobe/brackets/contributors?per_page={0}&page={1}", "extension_listing_url" : "", "extension_registry" : "https://s3.amazonaws.com/extend.brackets/registry.json", "extension_url" : "https://s3.amazonaws.com/extend.brackets/{0}/{0}-{1}.zip", "linting.enabled_by_default" : true, "build_timestamp" : "" } } ================================================ FILE: src/brackets.config.prerelease.json ================================================ { "healthDataServerURL" : "https://health.brackets.io/healthDataLog", "analyticsDataServerURL" : "https://cc-api-data.adobe.io/ingest", "serviceKey" : "brackets-service", "environment" : "production", "update_info_url" : "https://s3.amazonaws.com/files.brackets.io/updates/prerelease/.json", "notification_info_url" : "https://s3.amazonaws.com/files.brackets.io/notifications/prerelease/.json", "buildtype" : "prerelease" } ================================================ FILE: src/brackets.js ================================================ /* * Copyright (c) 2012 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /*global jQuery */ // TODO: (issue #264) break out the definition of brackets into a separate module from the application controller logic /** * brackets is the root of the Brackets codebase. This file pulls in all other modules as * dependencies (or dependencies thereof), initializes the UI, and binds global menus & keyboard * shortcuts to their Commands. * * Unlike other modules, this one can be accessed without an explicit require() because it exposes * a global object, window.brackets. */ define(function (require, exports, module) { "use strict"; // Load dependent non-module scripts require("widgets/bootstrap-dropdown"); require("widgets/bootstrap-modal"); require("widgets/bootstrap-twipsy-mod"); // Load CodeMirror add-ons--these attach themselves to the CodeMirror module require("thirdparty/CodeMirror/addon/edit/closebrackets"); require("thirdparty/CodeMirror/addon/edit/closetag"); require("thirdparty/CodeMirror/addon/edit/matchbrackets"); require("thirdparty/CodeMirror/addon/edit/matchtags"); require("thirdparty/CodeMirror/addon/fold/xml-fold"); require("thirdparty/CodeMirror/addon/mode/multiplex"); require("thirdparty/CodeMirror/addon/mode/overlay"); require("thirdparty/CodeMirror/addon/mode/simple"); require("thirdparty/CodeMirror/addon/scroll/scrollpastend"); require("thirdparty/CodeMirror/addon/search/match-highlighter"); require("thirdparty/CodeMirror/addon/search/searchcursor"); require("thirdparty/CodeMirror/addon/selection/active-line"); require("thirdparty/CodeMirror/addon/selection/mark-selection"); require("thirdparty/CodeMirror/keymap/sublime"); // Load dependent modules var AppInit = require("utils/AppInit"), LanguageManager = require("language/LanguageManager"), ProjectManager = require("project/ProjectManager"), FileViewController = require("project/FileViewController"), FileSyncManager = require("project/FileSyncManager"), Commands = require("command/Commands"), CommandManager = require("command/CommandManager"), PerfUtils = require("utils/PerfUtils"), FileSystem = require("filesystem/FileSystem"), Strings = require("strings"), Dialogs = require("widgets/Dialogs"), DefaultDialogs = require("widgets/DefaultDialogs"), ExtensionLoader = require("utils/ExtensionLoader"), Async = require("utils/Async"), UpdateNotification = require("utils/UpdateNotification"), UrlParams = require("utils/UrlParams").UrlParams, PreferencesManager = require("preferences/PreferencesManager"), DragAndDrop = require("utils/DragAndDrop"), NativeApp = require("utils/NativeApp"), DeprecationWarning = require("utils/DeprecationWarning"), ViewCommandHandlers = require("view/ViewCommandHandlers"), MainViewManager = require("view/MainViewManager"); var MainViewHTML = require("text!htmlContent/main-view.html"); // load modules for later use require("utils/Global"); require("editor/CSSInlineEditor"); require("project/WorkingSetSort"); require("search/QuickOpen"); require("search/QuickOpenHelper"); require("file/FileUtils"); require("project/SidebarView"); require("utils/Resizer"); require("LiveDevelopment/main"); require("utils/NodeConnection"); require("utils/NodeDomain"); require("utils/ColorUtils"); require("view/ThemeManager"); require("thirdparty/lodash"); require("language/XMLUtils"); require("language/JSONUtils"); require("widgets/InlineMenu"); // DEPRECATED: In future we want to remove the global CodeMirror, but for now we // expose our required CodeMirror globally so as to avoid breaking extensions in the // interim. var CodeMirror = require("thirdparty/CodeMirror/lib/codemirror"); Object.defineProperty(window, "CodeMirror", { get: function () { DeprecationWarning.deprecationWarning('Use brackets.getModule("thirdparty/CodeMirror/lib/codemirror") instead of global CodeMirror.', true); return CodeMirror; } }); // DEPRECATED: In future we want to remove the global Mustache, but for now we // expose our required Mustache globally so as to avoid breaking extensions in the // interim. var Mustache = require("thirdparty/mustache/mustache"); Object.defineProperty(window, "Mustache", { get: function () { DeprecationWarning.deprecationWarning('Use brackets.getModule("thirdparty/mustache/mustache") instead of global Mustache.', true); return Mustache; } }); // DEPRECATED: In future we want to remove the global PathUtils, but for now we // expose our required PathUtils globally so as to avoid breaking extensions in the // interim. var PathUtils = require("thirdparty/path-utils/path-utils"); Object.defineProperty(window, "PathUtils", { get: function () { DeprecationWarning.deprecationWarning('Use brackets.getModule("thirdparty/path-utils/path-utils") instead of global PathUtils.', true); return PathUtils; } }); //load language features require("features/ParameterHintsManager"); require("features/JumpToDefManager"); // Load modules that self-register and just need to get included in the main project require("command/DefaultMenus"); require("document/ChangedDocumentTracker"); require("editor/EditorCommandHandlers"); require("editor/EditorOptionHandlers"); require("editor/EditorStatusBar"); require("editor/ImageViewer"); require("extensibility/InstallExtensionDialog"); require("extensibility/ExtensionManagerDialog"); require("help/HelpCommandHandlers"); require("search/FindInFilesUI"); require("search/FindReplace"); //Load find References Feature Manager require("features/FindReferencesManager"); //Load common JS module require("JSUtils/Session"); require("JSUtils/ScopeManager"); //load Language Tools Module require("languageTools/PathConverters"); require("languageTools/LanguageTools"); require("languageTools/ClientLoader"); require("languageTools/BracketsToNodeInterface"); require("languageTools/DefaultProviders"); require("languageTools/DefaultEventHandlers"); PerfUtils.addMeasurement("brackets module dependencies resolved"); // Local variables var params = new UrlParams(); // read URL params params.parse(); /** * Setup test object */ function _initTest() { // TODO: (issue #265) Make sure the "test" object is not included in final builds // All modules that need to be tested from the context of the application // must to be added to this object. The unit tests cannot just pull // in the modules since they would run in context of the unit test window, // and would not have access to the app html/css. brackets.test = { CodeHintManager : require("editor/CodeHintManager"), CodeInspection : require("language/CodeInspection"), CommandManager : require("command/CommandManager"), Commands : require("command/Commands"), CSSUtils : require("language/CSSUtils"), DefaultDialogs : require("widgets/DefaultDialogs"), Dialogs : require("widgets/Dialogs"), DocumentCommandHandlers : require("document/DocumentCommandHandlers"), DocumentManager : require("document/DocumentManager"), DocumentModule : require("document/Document"), DOMAgent : require("LiveDevelopment/Agents/DOMAgent"), DragAndDrop : require("utils/DragAndDrop"), EditorManager : require("editor/EditorManager"), ExtensionLoader : require("utils/ExtensionLoader"), ExtensionUtils : require("utils/ExtensionUtils"), File : require("filesystem/File"), FileFilters : require("search/FileFilters"), FileSyncManager : require("project/FileSyncManager"), FileSystem : require("filesystem/FileSystem"), FileUtils : require("file/FileUtils"), FileViewController : require("project/FileViewController"), FindInFiles : require("search/FindInFiles"), FindInFilesUI : require("search/FindInFilesUI"), HTMLInstrumentation : require("language/HTMLInstrumentation"), Inspector : require("LiveDevelopment/Inspector/Inspector"), InstallExtensionDialog : require("extensibility/InstallExtensionDialog"), JSUtils : require("language/JSUtils"), KeyBindingManager : require("command/KeyBindingManager"), LanguageManager : require("language/LanguageManager"), LiveDevelopment : require("LiveDevelopment/LiveDevelopment"), LiveDevMultiBrowser : require("LiveDevelopment/LiveDevMultiBrowser"), LiveDevServerManager : require("LiveDevelopment/LiveDevServerManager"), MainViewFactory : require("view/MainViewFactory"), MainViewManager : require("view/MainViewManager"), Menus : require("command/Menus"), MultiRangeInlineEditor : require("editor/MultiRangeInlineEditor").MultiRangeInlineEditor, NativeApp : require("utils/NativeApp"), PerfUtils : require("utils/PerfUtils"), PreferencesManager : require("preferences/PreferencesManager"), ProjectManager : require("project/ProjectManager"), RemoteAgent : require("LiveDevelopment/Agents/RemoteAgent"), ScrollTrackMarkers : require("search/ScrollTrackMarkers"), UpdateNotification : require("utils/UpdateNotification"), WorkingSetView : require("project/WorkingSetView"), doneLoading : false }; AppInit.appReady(function () { brackets.test.doneLoading = true; }); } /** * Setup Brackets */ function _onReady() { PerfUtils.addMeasurement("window.document Ready"); // Let the user know Brackets doesn't run in a web browser yet if (brackets.inBrowser) { Dialogs.showModalDialog( DefaultDialogs.DIALOG_ID_ERROR, Strings.ERROR_IN_BROWSER_TITLE, Strings.ERROR_IN_BROWSER ); } brackets.app.getRemoteDebuggingPort(function (err, remote_debugging_port){ var InfoBar = require('widgets/infobar'), StringUtils = require("utils/StringUtils"); if ((!err) && remote_debugging_port && remote_debugging_port > 0) { InfoBar.showInfoBar({ type: "warning", title: `${Strings.REMOTE_DEBUGGING_ENABLED}${remote_debugging_port}`, description: "" }); } else if (err) { InfoBar.showInfoBar({ type: "error", title: StringUtils.format(Strings.REMOTE_DEBUGGING_PORT_INVALID, err, 1024, 65534), description: "" }); } }); // Use quiet scrollbars if we aren't on Lion. If we're on Lion, only // use native scroll bars when the mouse is not plugged in or when // using the "Always" scroll bar setting. var osxMatch = /Mac OS X 10\D([\d+])\D/.exec(window.navigator.userAgent); if (osxMatch && osxMatch[1] && Number(osxMatch[1]) >= 7) { // test a scrolling div for scrollbars var $testDiv = $("
    ").appendTo(window.document.body); if ($testDiv.outerWidth() === $testDiv.get(0).clientWidth) { $(".sidebar").removeClass("quiet-scrollbars"); } $testDiv.remove(); } // Load default languages and preferences Async.waitForAll([LanguageManager.ready, PreferencesManager.ready]).always(function () { // Load all extensions. This promise will complete even if one or more // extensions fail to load. var extensionPathOverride = params.get("extensions"); // used by unit tests var extensionLoaderPromise = ExtensionLoader.init(extensionPathOverride ? extensionPathOverride.split(",") : null); // Load the initial project after extensions have loaded extensionLoaderPromise.always(function () { // Signal that extensions are loaded AppInit._dispatchReady(AppInit.EXTENSIONS_LOADED); // Finish UI initialization ViewCommandHandlers.restoreFontSize(); var initialProjectPath = ProjectManager.getInitialProjectPath(); ProjectManager.openProject(initialProjectPath).always(function () { _initTest(); // If this is the first launch, and we have an index.html file in the project folder (which should be // the samples folder on first launch), open it automatically. (We explicitly check for the // samples folder in case this is the first time we're launching Brackets after upgrading from // an old version that might not have set the "afterFirstLaunch" pref.) var deferred = new $.Deferred(); if (!params.get("skipSampleProjectLoad") && !PreferencesManager.getViewState("afterFirstLaunch")) { PreferencesManager.setViewState("afterFirstLaunch", "true"); if (ProjectManager.isWelcomeProjectPath(initialProjectPath)) { FileSystem.resolve(initialProjectPath + "index.html", function (err, file) { if (!err) { var promise = CommandManager.execute(Commands.CMD_ADD_TO_WORKINGSET_AND_OPEN, { fullPath: file.fullPath }); promise.then(deferred.resolve, deferred.reject); } else { deferred.reject(); } }); } else { deferred.resolve(); } } else { deferred.resolve(); } deferred.always(function () { // Signal that Brackets is loaded AppInit._dispatchReady(AppInit.APP_READY); PerfUtils.addMeasurement("Application Startup"); if (PreferencesManager._isUserScopeCorrupt()) { var userPrefFullPath = PreferencesManager.getUserPrefFile(); // user scope can get corrupt only if the file exists, is readable, // but malformed. no need to check for its existance. var info = MainViewManager.findInAllWorkingSets(userPrefFullPath); var paneId; if (info.length) { paneId = info[0].paneId; } FileViewController.openFileAndAddToWorkingSet(userPrefFullPath, paneId) .done(function () { Dialogs.showModalDialog( DefaultDialogs.DIALOG_ID_ERROR, Strings.ERROR_PREFS_CORRUPT_TITLE, Strings.ERROR_PREFS_CORRUPT ).done(function () { // give the focus back to the editor with the pref file MainViewManager.focusActivePane(); }); }); } }); // See if any startup files were passed to the application if (brackets.app.getPendingFilesToOpen) { brackets.app.getPendingFilesToOpen(function (err, paths) { DragAndDrop.openDroppedFiles(paths); }); } }); }); }); // Check for updates if (!brackets.inBrowser && !params.get("skipUpdateCheck")) { AppInit.appReady(function () { // launches periodic checks for updates cca every 24 hours UpdateNotification.launchAutomaticUpdate(); }); } } /** * Setup event handlers prior to dispatching AppInit.HTML_READY */ function _beforeHTMLReady() { // Add the platform (mac, win or linux) to the body tag so we can have platform-specific CSS rules $("body").addClass("platform-" + brackets.platform); // Browser-hosted version may also have different CSS (e.g. since '#titlebar' is shown) if (brackets.inBrowser) { $("body").addClass("in-browser"); } else { $("body").addClass("in-appshell"); } // Enable/Disable HTML Menus if (brackets.nativeMenus) { $("body").addClass("has-appshell-menus"); } else { // (issue #5310) workaround for bootstrap dropdown: prevent the menu item to grab // the focus -- override jquery focus implementation for top-level menu items (function () { var defaultFocus = $.fn.focus; $.fn.focus = function () { if (!this.hasClass("dropdown-toggle")) { return defaultFocus.apply(this, arguments); } }; }()); } // Localize MainViewHTML and inject into tag $("body").html(Mustache.render(MainViewHTML, { shouldAddAA: (brackets.platform === "mac"), Strings: Strings })); // Update title $("title").text(brackets.config.app_title); // Respond to dragging & dropping files/folders onto the window by opening them. If we don't respond // to these events, the file would load in place of the Brackets UI DragAndDrop.attachHandlers(); // TODO: (issue 269) to support IE, need to listen to document instead (and even then it may not work when focus is in an input field?) $(window).focus(function () { // This call to syncOpenDocuments() *should* be a no-op now that we have // file watchers, but is still here as a safety net. FileSyncManager.syncOpenDocuments(); }); // Prevent unhandled middle button clicks from triggering native behavior // Example: activating AutoScroll (see #510) $("html").on("mousedown", ".inline-widget", function (e) { if (e.button === 1) { e.preventDefault(); } }); // The .no-focus style is added to clickable elements that should // not steal focus. Calling preventDefault() on mousedown prevents // focus from going to the click target. $("html").on("mousedown", ".no-focus", function (e) { // Text fields should always be focusable. var $target = $(e.target), isFormElement = $target.is("input") || $target.is("textarea") || $target.is("select"); if (!isFormElement) { e.preventDefault(); } }); // Prevent clicks on any link from navigating to a different page (which could lose unsaved // changes). We can't use a simple .on("click", "a") because of http://bugs.jquery.com/ticket/3861: // jQuery hides non-left clicks from such event handlers, yet middle-clicks still cause CEF to // navigate. Also, a capture handler is more reliable than bubble. window.document.body.addEventListener("click", function (e) { // Check parents too, in case link has inline formatting tags var node = e.target, url; while (node) { if (node.tagName === "A") { url = node.getAttribute("href"); if (url && !url.match(/^#/)) { NativeApp.openURLInDefaultBrowser(url); } e.preventDefault(); break; } node = node.parentElement; } }, true); // Prevent extensions from using window.open() to insecurely load untrusted web content var real_windowOpen = window.open; window.open = function (url) { // Allow file:// URLs, relative URLs (implicitly file: also), and about:blank if (!url.match(/^file:\/\//) && !url.match(/^about:blank/) && url.indexOf(":") !== -1) { throw new Error("Brackets-shell is not a secure general purpose web browser. Use NativeApp.openURLInDefaultBrowser() to open URLs in the user's main browser"); } return real_windowOpen.apply(window, arguments); }; // jQuery patch to shim deprecated usage of $() on EventDispatchers var DefaultCtor = jQuery.fn.init; jQuery.fn.init = function (firstArg, secondArg) { var jQObject = new DefaultCtor(firstArg, secondArg); // Is this a Brackets EventDispatcher object? (not a DOM node or other object) if (firstArg && firstArg._EventDispatcher) { // Patch the jQ wrapper object so it calls EventDispatcher's APIs instead of jQuery's jQObject.on = firstArg.on.bind(firstArg); jQObject.one = firstArg.one.bind(firstArg); jQObject.off = firstArg.off.bind(firstArg); // Don't offer legacy support for trigger()/triggerHandler() on core model objects; extensions // shouldn't be doing that anyway since it's basically poking at private API // Console warning, since $() is deprecated for EventDispatcher objects // (pass true to only print once per caller, and index 4 since the extension caller is deeper in the stack than usual) DeprecationWarning.deprecationWarning("Deprecated: Do not use $().on/off() on Brackets modules and model objects. Call on()/off() directly on the object without a $() wrapper.", true, 4); } return jQObject; }; } // Wait for view state to load. var viewStateTimer = PerfUtils.markStart("User viewstate loading"); PreferencesManager._smUserScopeLoading.always(function () { PerfUtils.addMeasurement(viewStateTimer); // Dispatch htmlReady event _beforeHTMLReady(); AppInit._dispatchReady(AppInit.HTML_READY); $(window.document).ready(_onReady); }); }); ================================================ FILE: src/command/CommandManager.js ================================================ /* * Copyright (c) 2012 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /** * Manages global application commands that can be called from menu items, key bindings, or subparts * of the application. * * This module dispatches these event(s): * - commandRegistered -- when a new command is registered * - beforeExecuteCommand -- before dispatching a command */ define(function (require, exports, module) { "use strict"; var EventDispatcher = require("utils/EventDispatcher"); /** * Map of all registered global commands * @type {Object.} */ var _commands = {}; /** * Temporary copy of commands map for restoring after testing * TODO (issue #1039): implement separate require contexts for unit tests * @type {Object.} */ var _commandsOriginal = {}; /** * Events: * - enabledStateChange * - checkedStateChange * - keyBindingAdded * - keyBindingRemoved * * @constructor * @private * @param {string} name - text that will be displayed in the UI to represent command * @param {string} id * @param {function} commandFn - the function that is called when the command is executed. * * TODO: where should this be triggered, The Command or Exports? */ function Command(name, id, commandFn) { this._name = name; this._id = id; this._commandFn = commandFn; this._checked = undefined; this._enabled = true; } EventDispatcher.makeEventDispatcher(Command.prototype); /** * Get command id * @return {string} */ Command.prototype.getID = function () { return this._id; }; /** * Executes the command. Additional arguments are passed to the executing function * * @return {$.Promise} a jQuery promise that will be resolved when the command completes. */ Command.prototype.execute = function () { if (!this._enabled) { return (new $.Deferred()).reject().promise(); } var result = this._commandFn.apply(this, arguments); if (!result) { // If command does not return a promise, assume that it handled the // command and return a resolved promise return (new $.Deferred()).resolve().promise(); } else { return result; } }; /** * Is command enabled? * @return {boolean} */ Command.prototype.getEnabled = function () { return this._enabled; }; /** * Sets enabled state of Command and dispatches "enabledStateChange" * when the enabled state changes. * @param {boolean} enabled */ Command.prototype.setEnabled = function (enabled) { var changed = this._enabled !== enabled; this._enabled = enabled; if (changed) { this.trigger("enabledStateChange"); } }; /** * Sets enabled state of Command and dispatches "checkedStateChange" * when the enabled state changes. * @param {boolean} checked */ Command.prototype.setChecked = function (checked) { var changed = this._checked !== checked; this._checked = checked; if (changed) { this.trigger("checkedStateChange"); } }; /** * Is command checked? * @return {boolean} */ Command.prototype.getChecked = function () { return this._checked; }; /** * Sets the name of the Command and dispatches "nameChange" so that * UI that reflects the command name can update. * * Note, a Command name can appear in either HTML or native UI * so HTML tags should not be used. To add a Unicode character, * use \uXXXX instead of an HTML entity. * * @param {string} name */ Command.prototype.setName = function (name) { var changed = this._name !== name; this._name = name; if (changed) { this.trigger("nameChange"); } }; /** * Get command name * @return {string} */ Command.prototype.getName = function () { return this._name; }; /** * Registers a global command. * @param {string} name - text that will be displayed in the UI to represent command * @param {string} id - unique identifier for command. * Core commands in Brackets use a simple command title as an id, for example "open.file". * Extensions should use the following format: "author.myextension.mycommandname". * For example, "lschmitt.csswizard.format.css". * @param {function(...)} commandFn - the function to call when the command is executed. Any arguments passed to * execute() (after the id) are passed as arguments to the function. If the function is asynchronous, * it must return a jQuery promise that is resolved when the command completes. Otherwise, the * CommandManager will assume it is synchronous, and return a promise that is already resolved. * @return {?Command} */ function register(name, id, commandFn) { if (_commands[id]) { console.log("Attempting to register an already-registered command: " + id); return null; } if (!name || !id || !commandFn) { console.error("Attempting to register a command with a missing name, id, or command function:" + name + " " + id); return null; } var command = new Command(name, id, commandFn); _commands[id] = command; exports.trigger("commandRegistered", command); return command; } /** * Registers a global internal only command. * @param {string} id - unique identifier for command. * Core commands in Brackets use a simple command title as an id, for example "app.abort_quit". * Extensions should use the following format: "author.myextension.mycommandname". * For example, "lschmitt.csswizard.format.css". * @param {function(...)} commandFn - the function to call when the command is executed. Any arguments passed to * execute() (after the id) are passed as arguments to the function. If the function is asynchronous, * it must return a jQuery promise that is resolved when the command completes. Otherwise, the * CommandManager will assume it is synchronous, and return a promise that is already resolved. * @return {?Command} */ function registerInternal(id, commandFn) { if (_commands[id]) { console.log("Attempting to register an already-registered command: " + id); return null; } if (!id || !commandFn) { console.error("Attempting to register an internal command with a missing id, or command function: " + id); return null; } var command = new Command(null, id, commandFn); _commands[id] = command; exports.trigger("commandRegistered", command); return command; } /** * Clear all commands for unit testing, but first make copy of commands so that * they can be restored afterward */ function _testReset() { _commandsOriginal = _commands; _commands = {}; } /** * Restore original commands after test and release copy */ function _testRestore() { _commands = _commandsOriginal; _commandsOriginal = {}; } /** * Retrieves a Command object by id * @param {string} id * @return {Command} */ function get(id) { return _commands[id]; } /** * Returns the ids of all registered commands * @return {Array.} */ function getAll() { return Object.keys(_commands); } /** * Looks up and runs a global command. Additional arguments are passed to the command. * * @param {string} id The ID of the command to run. * @return {$.Promise} a jQuery promise that will be resolved when the command completes. */ function execute(id) { var command = _commands[id]; if (command) { try { exports.trigger("beforeExecuteCommand", id); } catch (err) { console.error(err); } return command.execute.apply(command, Array.prototype.slice.call(arguments, 1)); } else { return (new $.Deferred()).reject().promise(); } } EventDispatcher.makeEventDispatcher(exports); // Define public API exports.register = register; exports.registerInternal = registerInternal; exports.execute = execute; exports.get = get; exports.getAll = getAll; exports._testReset = _testReset; exports._testRestore = _testRestore; }); ================================================ FILE: src/command/Commands.js ================================================ /* * Copyright (c) 2012 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ define(function (require, exports, module) { "use strict"; var DeprecationWarning = require("utils/DeprecationWarning"); /** * List of constants for global command IDs. */ // FILE exports.FILE_NEW_UNTITLED = "file.newDoc"; // DocumentCommandHandlers.js handleFileNew() exports.FILE_NEW = "file.newFile"; // DocumentCommandHandlers.js handleFileNewInProject() exports.FILE_NEW_FOLDER = "file.newFolder"; // DocumentCommandHandlers.js handleNewFolderInProject() exports.FILE_OPEN = "file.open"; // DocumentCommandHandlers.js handleDocumentOpen() exports.FILE_OPEN_FOLDER = "file.openFolder"; // ProjectManager.js openProject() exports.FILE_SAVE = "file.save"; // DocumentCommandHandlers.js handleFileSave() exports.FILE_SAVE_ALL = "file.saveAll"; // DocumentCommandHandlers.js handleFileSaveAll() exports.FILE_SAVE_AS = "file.saveAs"; // DocumentCommandHandlers.js handleFileSaveAs() exports.FILE_CLOSE = "file.close"; // DocumentCommandHandlers.js handleFileClose() exports.FILE_CLOSE_ALL = "file.close_all"; // DocumentCommandHandlers.js handleFileCloseAll() exports.FILE_CLOSE_LIST = "file.close_list"; // DocumentCommandHandlers.js handleFileCloseList() exports.FILE_OPEN_DROPPED_FILES = "file.openDroppedFiles"; // DragAndDrop.js openDroppedFiles() exports.FILE_LIVE_FILE_PREVIEW = "file.liveFilePreview"; // LiveDevelopment/main.js _handleGoLiveCommand() exports.TOGGLE_LIVE_PREVIEW_MB_MODE = "file.toggleLivePreviewMB"; // LiveDevelopment/main.js _toggleLivePreviewMultiBrowser() exports.CMD_RELOAD_LIVE_PREVIEW = "file.reloadLivePreview"; // LiveDevelopment/main.js _handleReloadLivePreviewCommand() exports.FILE_LIVE_HIGHLIGHT = "file.previewHighlight"; // LiveDevelopment/main.js _handlePreviewHighlightCommand() exports.FILE_PROJECT_SETTINGS = "file.projectSettings"; // ProjectManager.js _projectSettings() exports.FILE_RENAME = "file.rename"; // DocumentCommandHandlers.js handleFileRename() exports.FILE_DELETE = "file.delete"; // DocumentCommandHandlers.js handleFileDelete() exports.FILE_EXTENSION_MANAGER = "file.extensionManager"; // ExtensionManagerDialog.js _showDialog() exports.FILE_REFRESH = "file.refresh"; // ProjectManager.js refreshFileTree() exports.FILE_OPEN_PREFERENCES = "file.openPreferences"; // PreferencesManager.js _handleOpenPreferences() exports.FILE_OPEN_KEYMAP = "file.openKeyMap"; // KeyBindingManager.js _openUserKeyMap() // File shell callbacks - string must MATCH string in native code (appshell/command_callbacks.h) exports.FILE_CLOSE_WINDOW = "file.close_window"; // DocumentCommandHandlers.js handleFileCloseWindow() exports.FILE_QUIT = "file.quit"; // DocumentCommandHandlers.js handleFileQuit() // EDIT // File shell callbacks - string must MATCH string in native code (appshell/command_callbacks.h) exports.EDIT_UNDO = "edit.undo"; // EditorCommandHandlers.js handleUndo() exports.EDIT_REDO = "edit.redo"; // EditorCommandHandlers.js handleRedo() exports.EDIT_CUT = "edit.cut"; // EditorCommandHandlers.js ignoreCommand() exports.EDIT_COPY = "edit.copy"; // EditorCommandHandlers.js ignoreCommand() exports.EDIT_PASTE = "edit.paste"; // EditorCommandHandlers.js ignoreCommand() exports.EDIT_SELECT_ALL = "edit.selectAll"; // EditorCommandHandlers.js _handleSelectAll() exports.EDIT_SELECT_LINE = "edit.selectLine"; // EditorCommandHandlers.js selectLine() exports.EDIT_SPLIT_SEL_INTO_LINES = "edit.splitSelIntoLines"; // EditorCommandHandlers.js splitSelIntoLines() exports.EDIT_ADD_CUR_TO_NEXT_LINE = "edit.addCursorToNextLine"; // EditorCommandHandlers.js addCursorToNextLine() exports.EDIT_ADD_CUR_TO_PREV_LINE = "edit.addCursorToPrevLine"; // EditorCommandHandlers.js addCursorToPrevLine() exports.EDIT_INDENT = "edit.indent"; // EditorCommandHandlers.js indentText() exports.EDIT_UNINDENT = "edit.unindent"; // EditorCommandHandlers.js unindentText() exports.EDIT_DUPLICATE = "edit.duplicate"; // EditorCommandHandlers.js duplicateText() exports.EDIT_DELETE_LINES = "edit.deletelines"; // EditorCommandHandlers.js deleteCurrentLines() exports.EDIT_LINE_COMMENT = "edit.lineComment"; // EditorCommandHandlers.js lineComment() exports.EDIT_BLOCK_COMMENT = "edit.blockComment"; // EditorCommandHandlers.js blockComment() exports.EDIT_LINE_UP = "edit.lineUp"; // EditorCommandHandlers.js moveLineUp() exports.EDIT_LINE_DOWN = "edit.lineDown"; // EditorCommandHandlers.js moveLineDown() exports.EDIT_OPEN_LINE_ABOVE = "edit.openLineAbove"; // EditorCommandHandlers.js openLineAbove() exports.EDIT_OPEN_LINE_BELOW = "edit.openLineBelow"; // EditorCommandHandlers.js openLineBelow() exports.TOGGLE_CLOSE_BRACKETS = "edit.autoCloseBrackets"; // EditorOptionHandlers.js _getToggler() exports.SHOW_CODE_HINTS = "edit.showCodeHints"; // CodeHintManager.js _startNewSession() // FIND exports.CMD_FIND = "cmd.find"; // FindReplace.js _launchFind() exports.CMD_FIND_IN_FILES = "cmd.findInFiles"; // FindInFilesUI.js _showFindBar() exports.CMD_FIND_IN_SUBTREE = "cmd.findInSubtree"; // FindInFilesUI.js _showFindBarForSubtree() exports.CMD_FIND_NEXT = "cmd.findNext"; // FindReplace.js _findNext() exports.CMD_FIND_PREVIOUS = "cmd.findPrevious"; // FindReplace.js _findPrevious() exports.CMD_FIND_ALL_AND_SELECT = "cmd.findAllAndSelect"; // FindReplace.js _findAllAndSelect() exports.CMD_ADD_NEXT_MATCH = "cmd.addNextMatch"; // FindReplace.js _expandAndAddNextToSelection() exports.CMD_SKIP_CURRENT_MATCH = "cmd.skipCurrentMatch"; // FindReplace.js _skipCurrentMatch() exports.CMD_REPLACE = "cmd.replace"; // FindReplace.js _replace() exports.CMD_REPLACE_IN_FILES = "cmd.replaceInFiles"; // FindInFilesUI.js _showReplaceBar() exports.CMD_REPLACE_IN_SUBTREE = "cmd.replaceInSubtree"; // FindInFilesUI.js _showReplaceBarForSubtree() exports.CMD_FIND_ALL_REFERENCES = "cmd.findAllReferences"; // findReferencesManager.js _openReferencesPanel() // VIEW exports.CMD_THEMES_OPEN_SETTINGS = "view.themesOpenSetting"; // MenuCommands.js Settings.open() exports.VIEW_HIDE_SIDEBAR = "view.toggleSidebar"; // SidebarView.js toggle() exports.VIEW_INCREASE_FONT_SIZE = "view.increaseFontSize"; // ViewCommandHandlers.js _handleIncreaseFontSize() exports.VIEW_DECREASE_FONT_SIZE = "view.decreaseFontSize"; // ViewCommandHandlers.js _handleDecreaseFontSize() exports.VIEW_RESTORE_FONT_SIZE = "view.restoreFontSize"; // ViewCommandHandlers.js _handleRestoreFontSize() exports.VIEW_SCROLL_LINE_UP = "view.scrollLineUp"; // ViewCommandHandlers.js _handleScrollLineUp() exports.VIEW_SCROLL_LINE_DOWN = "view.scrollLineDown"; // ViewCommandHandlers.js _handleScrollLineDown() exports.VIEW_TOGGLE_INSPECTION = "view.toggleCodeInspection"; // CodeInspection.js toggleEnabled() exports.TOGGLE_LINE_NUMBERS = "view.toggleLineNumbers"; // EditorOptionHandlers.js _getToggler() exports.TOGGLE_ACTIVE_LINE = "view.toggleActiveLine"; // EditorOptionHandlers.js _getToggler() exports.TOGGLE_WORD_WRAP = "view.toggleWordWrap"; // EditorOptionHandlers.js _getToggler() exports.TOGGLE_SEARCH_AUTOHIDE = "view.toggleSearchAutoHide"; // EditorOptionHandlers.js _getToggler() exports.CMD_OPEN = "cmd.open"; exports.CMD_ADD_TO_WORKINGSET_AND_OPEN = "cmd.addToWorkingSetAndOpen"; // DocumentCommandHandlers.js handleOpenDocumentInNewPane() // NAVIGATE exports.NAVIGATE_NEXT_DOC = "navigate.nextDoc"; // DocumentCommandHandlers.js handleGoNextDoc() exports.NAVIGATE_PREV_DOC = "navigate.prevDoc"; // DocumentCommandHandlers.js handleGoPrevDoc() exports.NAVIGATE_NEXT_DOC_LIST_ORDER = "navigate.nextDocListOrder"; // DocumentCommandHandlers.js handleGoNextDocListOrder() exports.NAVIGATE_PREV_DOC_LIST_ORDER = "navigate.prevDocListOrder"; // DocumentCommandHandlers.js handleGoPrevDocListOrder() exports.NAVIGATE_SHOW_IN_FILE_TREE = "navigate.showInFileTree"; // DocumentCommandHandlers.js handleShowInTree() exports.NAVIGATE_SHOW_IN_OS = "navigate.showInOS"; // DocumentCommandHandlers.js handleShowInOS() exports.NAVIGATE_QUICK_OPEN = "navigate.quickOpen"; // QuickOpen.js doFileSearch() exports.NAVIGATE_JUMPTO_DEFINITION = "navigate.jumptoDefinition"; // JumpToDefManager.js _doJumpToDef() exports.NAVIGATE_GOTO_DEFINITION = "navigate.gotoDefinition"; // QuickOpen.js doDefinitionSearch() exports.NAVIGATE_GOTO_DEFINITION_PROJECT = "navigate.gotoDefinitionInProject"; // QuickOpen.js doDefinitionSearchInProject() exports.NAVIGATE_GOTO_LINE = "navigate.gotoLine"; // QuickOpen.js doGotoLine() exports.NAVIGATE_GOTO_FIRST_PROBLEM = "navigate.gotoFirstProblem"; // CodeInspection.js handleGotoFirstProblem() exports.TOGGLE_QUICK_EDIT = "navigate.toggleQuickEdit"; // EditorManager.js _toggleInlineWidget() exports.TOGGLE_QUICK_DOCS = "navigate.toggleQuickDocs"; // EditorManager.js _toggleInlineWidget() exports.QUICK_EDIT_NEXT_MATCH = "navigate.nextMatch"; // MultiRangeInlineEditor.js _nextRange() exports.QUICK_EDIT_PREV_MATCH = "navigate.previousMatch"; // MultiRangeInlineEditor.js _previousRange() exports.CSS_QUICK_EDIT_NEW_RULE = "navigate.newRule"; // CSSInlineEditor.js _handleNewRule() // HELP exports.HELP_CHECK_FOR_UPDATE = "help.checkForUpdate"; // HelpCommandHandlers.js _handleCheckForUpdates() exports.HELP_HOW_TO_USE_BRACKETS = "help.howToUseBrackets"; // HelpCommandHandlers.js _handleLinkMenuItem() exports.HELP_SUPPORT = "help.support"; // HelpCommandHandlers.js _handleLinkMenuItem() exports.HELP_SUGGEST = "help.suggest"; // HelpCommandHandlers.js _handleLinkMenuItem() exports.HELP_RELEASE_NOTES = "help.releaseNotes"; // HelpCommandHandlers.js _handleLinkMenuItem() exports.HELP_GET_INVOLVED = "help.getInvolved"; // HelpCommandHandlers.js _handleLinkMenuItem() exports.HELP_SHOW_EXT_FOLDER = "help.showExtensionsFolder"; // HelpCommandHandlers.js _handleShowExtensionsFolder() exports.HELP_HOMEPAGE = "help.homepage"; // HelpCommandHandlers.js _handleLinkMenuItem() exports.HELP_TWITTER = "help.twitter"; // HelpCommandHandlers.js _handleLinkMenuItem() // Working Set Configuration exports.CMD_WORKINGSET_SORT_BY_ADDED = "cmd.sortWorkingSetByAdded"; // WorkingSetSort.js _handleSort() exports.CMD_WORKINGSET_SORT_BY_NAME = "cmd.sortWorkingSetByName"; // WorkingSetSort.js _handleSort() exports.CMD_WORKINGSET_SORT_BY_TYPE = "cmd.sortWorkingSetByType"; // WorkingSetSort.js _handleSort() exports.CMD_WORKING_SORT_TOGGLE_AUTO = "cmd.sortWorkingSetToggleAuto"; // WorkingSetSort.js _handleToggleAutoSort() // Split View exports.CMD_SPLITVIEW_NONE = "cmd.splitViewNone"; // SidebarView.js _handleSplitNone() exports.CMD_SPLITVIEW_VERTICAL = "cmd.splitViewVertical"; // SidebarView.js _handleSplitVertical() exports.CMD_SPLITVIEW_HORIZONTAL = "cmd.splitViewHorizontal"; // SidebarView.js _handleSplitHorizontal() exports.CMD_SWITCH_PANE_FOCUS = "cmd.switchPaneFocus"; // MainViewManager.js _switchPaneFocus() // File shell callbacks - string must MATCH string in native code (appshell/command_callbacks.h) exports.HELP_ABOUT = "help.about"; // HelpCommandHandlers.js _handleAboutDialog() // APP exports.APP_RELOAD = "app.reload"; // DocumentCommandHandlers.js handleReload() exports.APP_RELOAD_WITHOUT_EXTS = "app.reload_without_exts"; // DocumentCommandHandlers.js handleReloadWithoutExts() // File shell callbacks - string must MATCH string in native code (appshell/command_callbacks.h) exports.APP_ABORT_QUIT = "app.abort_quit"; // DocumentCommandHandlers.js handleAbortQuit() exports.APP_BEFORE_MENUPOPUP = "app.before_menupopup"; // DocumentCommandHandlers.js handleBeforeMenuPopup() // ADD_TO_WORKING_SET is deprectated but we need a handler for it because the new command doesn't return the same result as the legacy command exports.FILE_ADD_TO_WORKING_SET = "file.addToWorkingSet"; // Deprecated through DocumentCommandHandlers.js handleFileAddToWorkingSet // Show or Hide sidebar exports.HIDE_SIDEBAR = "view.hideSidebar"; // SidebarView.js hide() exports.SHOW_SIDEBAR = "view.showSidebar"; // SidebarView.js show() // DEPRECATED: Working Set Commands DeprecationWarning.deprecateConstant(exports, "SORT_WORKINGSET_BY_ADDED", "CMD_WORKINGSET_SORT_BY_ADDED"); DeprecationWarning.deprecateConstant(exports, "SORT_WORKINGSET_BY_NAME", "CMD_WORKINGSET_SORT_BY_NAME"); DeprecationWarning.deprecateConstant(exports, "SORT_WORKINGSET_BY_TYPE", "CMD_WORKINGSET_SORT_BY_TYPE"); DeprecationWarning.deprecateConstant(exports, "SORT_WORKINGSET_AUTO", "CMD_WORKING_SORT_TOGGLE_AUTO"); }); ================================================ FILE: src/command/DefaultMenus.js ================================================ /* * Copyright (c) 2013 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /** * Initializes the default brackets menu items. */ define(function (require, exports, module) { "use strict"; var AppInit = require("utils/AppInit"), Commands = require("command/Commands"), Menus = require("command/Menus"), Strings = require("strings"), MainViewManager = require("view/MainViewManager"), CommandManager = require("command/CommandManager"); /** * Disables menu items present in items if enabled is true. * enabled is true if file is saved and present on user system. * @param {boolean} enabled * @param {array} items */ function _setContextMenuItemsVisible(enabled, items) { items.forEach(function (item) { CommandManager.get(item).setEnabled(enabled); }); } /** * Checks if file saved and present on system and * disables menu items accordingly */ function _setMenuItemsVisible() { var file = MainViewManager.getCurrentlyViewedFile(MainViewManager.ACTIVE_PANE); if (file) { file.exists(function (err, isPresent) { if (err) { return err; } _setContextMenuItemsVisible(isPresent, [Commands.FILE_RENAME, Commands.NAVIGATE_SHOW_IN_FILE_TREE, Commands.NAVIGATE_SHOW_IN_OS]); }); } } AppInit.htmlReady(function () { /* * File menu */ var menu; menu = Menus.addMenu(Strings.FILE_MENU, Menus.AppMenuBar.FILE_MENU); menu.addMenuItem(Commands.FILE_NEW_UNTITLED); menu.addMenuItem(Commands.FILE_OPEN); menu.addMenuItem(Commands.FILE_OPEN_FOLDER); menu.addMenuItem(Commands.FILE_CLOSE); menu.addMenuItem(Commands.FILE_CLOSE_ALL); menu.addMenuDivider(); menu.addMenuItem(Commands.FILE_SAVE); menu.addMenuItem(Commands.FILE_SAVE_ALL); menu.addMenuItem(Commands.FILE_SAVE_AS); menu.addMenuDivider(); menu.addMenuItem(Commands.FILE_LIVE_FILE_PREVIEW); menu.addMenuItem(Commands.TOGGLE_LIVE_PREVIEW_MB_MODE); menu.addMenuItem(Commands.FILE_PROJECT_SETTINGS); menu.addMenuDivider(); menu.addMenuItem(Commands.FILE_EXTENSION_MANAGER); // suppress redundant quit menu item on mac if (brackets.platform !== "mac" || !brackets.nativeMenus) { menu.addMenuDivider(); menu.addMenuItem(Commands.FILE_QUIT); } /* * Edit menu */ menu = Menus.addMenu(Strings.EDIT_MENU, Menus.AppMenuBar.EDIT_MENU); menu.addMenuItem(Commands.EDIT_UNDO); menu.addMenuItem(Commands.EDIT_REDO); menu.addMenuDivider(); if (brackets.nativeMenus) { // Native-only - can't programmatically trigger clipboard actions from JS menus menu.addMenuItem(Commands.EDIT_CUT); menu.addMenuItem(Commands.EDIT_COPY); menu.addMenuItem(Commands.EDIT_PASTE); menu.addMenuDivider(); } menu.addMenuItem(Commands.EDIT_SELECT_ALL); menu.addMenuItem(Commands.EDIT_SELECT_LINE); menu.addMenuItem(Commands.EDIT_SPLIT_SEL_INTO_LINES); menu.addMenuItem(Commands.EDIT_ADD_CUR_TO_PREV_LINE); menu.addMenuItem(Commands.EDIT_ADD_CUR_TO_NEXT_LINE); menu.addMenuDivider(); menu.addMenuItem(Commands.EDIT_INDENT); menu.addMenuItem(Commands.EDIT_UNINDENT); menu.addMenuItem(Commands.EDIT_DUPLICATE); menu.addMenuItem(Commands.EDIT_DELETE_LINES); menu.addMenuItem(Commands.EDIT_LINE_UP); menu.addMenuItem(Commands.EDIT_LINE_DOWN); menu.addMenuDivider(); menu.addMenuItem(Commands.EDIT_LINE_COMMENT); menu.addMenuItem(Commands.EDIT_BLOCK_COMMENT); menu.addMenuDivider(); menu.addMenuItem(Commands.SHOW_CODE_HINTS); menu.addMenuDivider(); menu.addMenuItem(Commands.TOGGLE_CLOSE_BRACKETS); /* * Find menu */ menu = Menus.addMenu(Strings.FIND_MENU, Menus.AppMenuBar.FIND_MENU); menu.addMenuItem(Commands.CMD_FIND); menu.addMenuItem(Commands.CMD_FIND_NEXT); menu.addMenuItem(Commands.CMD_FIND_PREVIOUS); menu.addMenuItem(Commands.CMD_FIND_ALL_AND_SELECT); menu.addMenuItem(Commands.CMD_ADD_NEXT_MATCH); menu.addMenuItem(Commands.CMD_SKIP_CURRENT_MATCH); menu.addMenuDivider(); menu.addMenuItem(Commands.CMD_FIND_IN_FILES); menu.addMenuItem(Commands.CMD_FIND_ALL_REFERENCES); menu.addMenuDivider(); menu.addMenuItem(Commands.CMD_REPLACE); menu.addMenuItem(Commands.CMD_REPLACE_IN_FILES); /* * View menu */ menu = Menus.addMenu(Strings.VIEW_MENU, Menus.AppMenuBar.VIEW_MENU); menu.addMenuItem(Commands.CMD_THEMES_OPEN_SETTINGS); menu.addMenuDivider(); menu.addMenuItem(Commands.CMD_SPLITVIEW_NONE); menu.addMenuItem(Commands.CMD_SPLITVIEW_VERTICAL); menu.addMenuItem(Commands.CMD_SPLITVIEW_HORIZONTAL); menu.addMenuDivider(); menu.addMenuItem(Commands.VIEW_HIDE_SIDEBAR); menu.addMenuItem(Commands.TOGGLE_SEARCH_AUTOHIDE); menu.addMenuDivider(); menu.addMenuItem(Commands.VIEW_INCREASE_FONT_SIZE); menu.addMenuItem(Commands.VIEW_DECREASE_FONT_SIZE); menu.addMenuItem(Commands.VIEW_RESTORE_FONT_SIZE); menu.addMenuDivider(); menu.addMenuItem(Commands.TOGGLE_ACTIVE_LINE); menu.addMenuItem(Commands.TOGGLE_LINE_NUMBERS); menu.addMenuItem(Commands.TOGGLE_WORD_WRAP); menu.addMenuDivider(); menu.addMenuItem(Commands.FILE_LIVE_HIGHLIGHT); menu.addMenuDivider(); menu.addMenuItem(Commands.VIEW_TOGGLE_INSPECTION); /* * Navigate menu */ menu = Menus.addMenu(Strings.NAVIGATE_MENU, Menus.AppMenuBar.NAVIGATE_MENU); menu.addMenuItem(Commands.NAVIGATE_QUICK_OPEN); menu.addMenuItem(Commands.NAVIGATE_GOTO_LINE); menu.addMenuItem(Commands.NAVIGATE_GOTO_DEFINITION); menu.addMenuItem(Commands.NAVIGATE_GOTO_DEFINITION_PROJECT); menu.addMenuItem(Commands.NAVIGATE_JUMPTO_DEFINITION); menu.addMenuItem(Commands.NAVIGATE_GOTO_FIRST_PROBLEM); menu.addMenuDivider(); menu.addMenuItem(Commands.NAVIGATE_NEXT_DOC); menu.addMenuItem(Commands.NAVIGATE_PREV_DOC); menu.addMenuItem(Commands.NAVIGATE_NEXT_DOC_LIST_ORDER); menu.addMenuItem(Commands.NAVIGATE_PREV_DOC_LIST_ORDER); menu.addMenuDivider(); menu.addMenuItem(Commands.NAVIGATE_SHOW_IN_FILE_TREE); menu.addMenuDivider(); menu.addMenuItem(Commands.TOGGLE_QUICK_EDIT); menu.addMenuItem(Commands.QUICK_EDIT_PREV_MATCH); menu.addMenuItem(Commands.QUICK_EDIT_NEXT_MATCH); menu.addMenuItem(Commands.CSS_QUICK_EDIT_NEW_RULE); menu.addMenuDivider(); menu.addMenuItem(Commands.TOGGLE_QUICK_DOCS); /* * Help menu */ menu = Menus.addMenu(Strings.HELP_MENU, Menus.AppMenuBar.HELP_MENU); menu.addMenuItem(Commands.HELP_CHECK_FOR_UPDATE); menu.addMenuDivider(); if (brackets.config.how_to_use_url) { menu.addMenuItem(Commands.HELP_HOW_TO_USE_BRACKETS); } if (brackets.config.support_url) { menu.addMenuItem(Commands.HELP_SUPPORT); } if (brackets.config.suggest_feature_url) { menu.addMenuItem(Commands.HELP_SUGGEST); } if (brackets.config.release_notes_url) { menu.addMenuItem(Commands.HELP_RELEASE_NOTES); } if (brackets.config.get_involved_url) { menu.addMenuItem(Commands.HELP_GET_INVOLVED); } menu.addMenuDivider(); menu.addMenuItem(Commands.HELP_SHOW_EXT_FOLDER); var hasAboutItem = (brackets.platform !== "mac" || !brackets.nativeMenus); // Add final divider only if we have a homepage URL or twitter URL or about item if (hasAboutItem || brackets.config.homepage_url || brackets.config.twitter_url) { menu.addMenuDivider(); } if (brackets.config.homepage_url) { menu.addMenuItem(Commands.HELP_HOMEPAGE); } if (brackets.config.twitter_url) { menu.addMenuItem(Commands.HELP_TWITTER); } // supress redundant about menu item in mac shell if (hasAboutItem) { menu.addMenuItem(Commands.HELP_ABOUT); } /* * Context Menus */ // WorkingSet context menu - Unlike most context menus, we can't attach // listeners here because the DOM nodes for each pane's working set are // created dynamically. Each WorkingSetView attaches its own listeners. var workingset_cmenu = Menus.registerContextMenu(Menus.ContextMenuIds.WORKING_SET_CONTEXT_MENU); workingset_cmenu.addMenuItem(Commands.FILE_SAVE); workingset_cmenu.addMenuItem(Commands.FILE_SAVE_AS); workingset_cmenu.addMenuItem(Commands.FILE_RENAME); workingset_cmenu.addMenuItem(Commands.NAVIGATE_SHOW_IN_FILE_TREE); workingset_cmenu.addMenuItem(Commands.NAVIGATE_SHOW_IN_OS); workingset_cmenu.addMenuDivider(); workingset_cmenu.addMenuItem(Commands.CMD_FIND_IN_SUBTREE); workingset_cmenu.addMenuItem(Commands.CMD_REPLACE_IN_SUBTREE); workingset_cmenu.addMenuDivider(); workingset_cmenu.addMenuItem(Commands.FILE_CLOSE); var workingset_configuration_menu = Menus.registerContextMenu(Menus.ContextMenuIds.WORKING_SET_CONFIG_MENU); workingset_configuration_menu.addMenuItem(Commands.CMD_WORKINGSET_SORT_BY_ADDED); workingset_configuration_menu.addMenuItem(Commands.CMD_WORKINGSET_SORT_BY_NAME); workingset_configuration_menu.addMenuItem(Commands.CMD_WORKINGSET_SORT_BY_TYPE); workingset_configuration_menu.addMenuDivider(); workingset_configuration_menu.addMenuItem(Commands.CMD_WORKING_SORT_TOGGLE_AUTO); var splitview_menu = Menus.registerContextMenu(Menus.ContextMenuIds.SPLITVIEW_MENU); splitview_menu.addMenuItem(Commands.CMD_SPLITVIEW_NONE); splitview_menu.addMenuItem(Commands.CMD_SPLITVIEW_VERTICAL); splitview_menu.addMenuItem(Commands.CMD_SPLITVIEW_HORIZONTAL); var project_cmenu = Menus.registerContextMenu(Menus.ContextMenuIds.PROJECT_MENU); project_cmenu.addMenuItem(Commands.FILE_NEW); project_cmenu.addMenuItem(Commands.FILE_NEW_FOLDER); project_cmenu.addMenuItem(Commands.FILE_RENAME); project_cmenu.addMenuItem(Commands.FILE_DELETE); project_cmenu.addMenuItem(Commands.NAVIGATE_SHOW_IN_OS); project_cmenu.addMenuDivider(); project_cmenu.addMenuItem(Commands.CMD_FIND_IN_SUBTREE); project_cmenu.addMenuItem(Commands.CMD_REPLACE_IN_SUBTREE); project_cmenu.addMenuDivider(); project_cmenu.addMenuItem(Commands.FILE_REFRESH); var editor_cmenu = Menus.registerContextMenu(Menus.ContextMenuIds.EDITOR_MENU); // editor_cmenu.addMenuItem(Commands.NAVIGATE_JUMPTO_DEFINITION); editor_cmenu.addMenuItem(Commands.TOGGLE_QUICK_EDIT); editor_cmenu.addMenuItem(Commands.TOGGLE_QUICK_DOCS); editor_cmenu.addMenuItem(Commands.CMD_FIND_ALL_REFERENCES); editor_cmenu.addMenuDivider(); editor_cmenu.addMenuItem(Commands.EDIT_CUT); editor_cmenu.addMenuItem(Commands.EDIT_COPY); editor_cmenu.addMenuItem(Commands.EDIT_PASTE); editor_cmenu.addMenuDivider(); editor_cmenu.addMenuItem(Commands.EDIT_SELECT_ALL); var inline_editor_cmenu = Menus.registerContextMenu(Menus.ContextMenuIds.INLINE_EDITOR_MENU); inline_editor_cmenu.addMenuItem(Commands.TOGGLE_QUICK_EDIT); inline_editor_cmenu.addMenuItem(Commands.EDIT_SELECT_ALL); inline_editor_cmenu.addMenuDivider(); inline_editor_cmenu.addMenuItem(Commands.QUICK_EDIT_PREV_MATCH); inline_editor_cmenu.addMenuItem(Commands.QUICK_EDIT_NEXT_MATCH); /** * Context menu for code editors (both full-size and inline) * Auto selects the word the user clicks if the click does not occur over * an existing selection */ $("#editor-holder").on("contextmenu", function (e) { require(["editor/EditorManager"], function (EditorManager) { if ($(e.target).parents(".CodeMirror-gutter").length !== 0) { return; } // Note: on mousedown before this event, CodeMirror automatically checks mouse pos, and // if not clicking on a selection moves the cursor to click location. When triggered // from keyboard, no pre-processing occurs and the cursor/selection is left as is. var editor = EditorManager.getFocusedEditor(), inlineWidget = EditorManager.getFocusedInlineWidget(); if (editor) { //if (!editor.hasSelection()) { // Prevent menu from overlapping text by moving it down a little // Temporarily backout this change for now to help mitigate issue #1111, // which only happens if mouse is not over context menu. Better fix // requires change to bootstrap, which is too risky for now. //e.pageY += 6; //} // Inline text editors have a different context menu (safe to assume it's not some other // type of inline widget since we already know an Editor has focus) if (inlineWidget) { inline_editor_cmenu.open(e); } else { editor_cmenu.open(e); } } }); }); /** * Context menu for folder tree */ $("#project-files-container").on("contextmenu", function (e) { project_cmenu.open(e); }); // Dropdown menu for workspace sorting Menus.ContextMenu.assignContextMenuToSelector(".working-set-option-btn", workingset_configuration_menu); // Dropdown menu for view splitting Menus.ContextMenu.assignContextMenuToSelector(".working-set-splitview-btn", splitview_menu); // Prevent the browser context menu since Brackets creates a custom context menu $(window).contextmenu(function (e) { e.preventDefault(); }); /* * General menu event processing */ // Prevent clicks on top level menus and menu items from taking focus $(window.document).on("mousedown", ".dropdown", function (e) { e.preventDefault(); }); // Switch menus when the mouse enters an adjacent menu // Only open the menu if another one has already been opened // by clicking $(window.document).on("mouseenter", "#titlebar .dropdown", function (e) { var open = $(this).siblings(".open"); if (open.length > 0) { open.removeClass("open"); $(this).addClass("open"); } }); // Check the visibility of context menu items before opening the context menu. // 'Rename', 'Show in file tree' and 'Show in explorer' items will be disabled for files that have not yet been saved to disk. Menus.getContextMenu(Menus.ContextMenuIds.WORKING_SET_CONTEXT_MENU).on("beforeContextMenuOpen", _setMenuItemsVisible); Menus.getContextMenu(Menus.ContextMenuIds.PROJECT_MENU).on("beforeContextMenuOpen", _setMenuItemsVisible); }); }); ================================================ FILE: src/command/KeyBindingManager.js ================================================ /* * Copyright (c) 2012 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /*jslint regexp: true */ /*unittests: KeyBindingManager */ /** * Manages the mapping of keyboard inputs to commands. */ define(function (require, exports, module) { "use strict"; require("utils/Global"); var AppInit = require("utils/AppInit"), Commands = require("command/Commands"), CommandManager = require("command/CommandManager"), DefaultDialogs = require("widgets/DefaultDialogs"), EventDispatcher = require("utils/EventDispatcher"), FileSystem = require("filesystem/FileSystem"), FileSystemError = require("filesystem/FileSystemError"), FileUtils = require("file/FileUtils"), KeyEvent = require("utils/KeyEvent"), Strings = require("strings"), StringUtils = require("utils/StringUtils"), UrlParams = require("utils/UrlParams").UrlParams, _ = require("thirdparty/lodash"); var KeyboardPrefs = JSON.parse(require("text!base-config/keyboard.json")); var KEYMAP_FILENAME = "keymap.json", _userKeyMapFilePath = brackets.app.getApplicationSupportDirectory() + "/" + KEYMAP_FILENAME; /** * @private * Maps normalized shortcut descriptor to key binding info. * @type {!Object.} */ var _keyMap = {}, // For the actual key bindings including user specified ones // For the default factory key bindings, cloned from _keyMap after all extensions are loaded. _defaultKeyMap = {}; /** * @typedef {{shortcut: !string, * commandID: ?string}} UserKeyBinding */ /** * @private * Maps shortcut descriptor to a command id. * @type {UserKeyBinding} */ var _customKeyMap = {}, _customKeyMapCache = {}; /** * @private * Maps commandID to the list of shortcuts that are bound to it. * @type {!Object.>} */ var _commandMap = {}; /** * @private * An array of command ID for all the available commands including the commands * of installed extensions. * @type {Array.} */ var _allCommands = []; /** * @private * Maps key names to the corresponding unicode symols * @type {{key: string, displayKey: string}} */ var _displayKeyMap = { "up": "\u2191", "down": "\u2193", "left": "\u2190", "right": "\u2192", "-": "\u2212" }; var _specialCommands = [Commands.EDIT_UNDO, Commands.EDIT_REDO, Commands.EDIT_SELECT_ALL, Commands.EDIT_CUT, Commands.EDIT_COPY, Commands.EDIT_PASTE], _reservedShortcuts = ["Ctrl-Z", "Ctrl-Y", "Ctrl-A", "Ctrl-X", "Ctrl-C", "Ctrl-V"], _macReservedShortcuts = ["Cmd-,", "Cmd-H", "Cmd-Alt-H", "Cmd-M", "Cmd-Shift-Z", "Cmd-Q"], _keyNames = ["Up", "Down", "Left", "Right", "Backspace", "Enter", "Space", "Tab", "PageUp", "PageDown", "Home", "End", "Insert", "Delete"]; /** * @private * Flag to show key binding errors in the key map file. Default is true and * it will be set to false when reloading without extensions. This flag is not * used to suppress errors in loading or parsing the key map file. So if the key * map file is corrupt, then the error dialog still shows up. * * @type {boolean} */ var _showErrors = true; /** * @private * Allow clients to toggle key binding * @type {boolean} */ var _enabled = true; /** * @private * Stack of registered global keydown hooks. * @type {Array.} */ var _globalKeydownHooks = []; /** * @private * Forward declaration for JSLint. * @type {Function} */ var _loadUserKeyMap; /** * @private * States of Ctrl key down detection * @enum {number} */ var CtrlDownStates = { "NOT_YET_DETECTED" : 0, "DETECTED" : 1, "DETECTED_AND_IGNORED": 2 // For consecutive ctrl keydown events while a Ctrl key is being hold down }; /** * @private * Flags used to determine whether right Alt key is pressed. When it is pressed, * the following two keydown events are triggered in that specific order. * * 1. _ctrlDown - flag used to record { ctrlKey: true, keyIdentifier: "Control", ... } keydown event * 2. _altGrDown - flag used to record { ctrlKey: true, altKey: true, keyIdentifier: "Alt", ... } keydown event * * @type {CtrlDownStates|boolean} */ var _ctrlDown = CtrlDownStates.NOT_YET_DETECTED, _altGrDown = false; /** * @private * Used to record the timeStamp property of the last keydown event. * @type {number} */ var _lastTimeStamp; /** * @private * Used to record the keyIdentifier property of the last keydown event. * @type {string} */ var _lastKeyIdentifier; /* * @private * Constant used for checking the interval between Control keydown event and Alt keydown event. * If the right Alt key is down we get Control keydown followed by Alt keydown within 30 ms. if * the user is pressing Control key and then Alt key, the interval will be larger than 30 ms. * @type {number} */ var MAX_INTERVAL_FOR_CTRL_ALT_KEYS = 30; /** * @private * Forward declaration for JSLint. * @type {Function} */ var _onCtrlUp; /** * @private * Resets all the flags and removes _onCtrlUp event listener. * */ function _quitAltGrMode() { _enabled = true; _ctrlDown = CtrlDownStates.NOT_YET_DETECTED; _altGrDown = false; _lastTimeStamp = null; _lastKeyIdentifier = null; $(window).off("keyup", _onCtrlUp); } /** * @private * Detects the release of AltGr key by checking all keyup events * until we receive one with ctrl key code. Once detected, reset * all the flags and also remove this event listener. * * @param {!KeyboardEvent} e keyboard event object */ _onCtrlUp = function (e) { var key = e.keyCode || e.which; if (_altGrDown && key === KeyEvent.DOM_VK_CONTROL) { _quitAltGrMode(); } }; /** * @private * Detects whether AltGr key is pressed. When it is pressed, the first keydown event has * ctrlKey === true with keyIdentifier === "Control". The next keydown event with * altKey === true, ctrlKey === true and keyIdentifier === "Alt" is sent within 30 ms. Then * the next keydown event with altKey === true, ctrlKey === true and keyIdentifier === "Control" * is sent. If the user keep holding AltGr key down, then the second and third * keydown events are repeatedly sent out alternately. If the user is also holding down Ctrl * key, then either keyIdentifier === "Control" or keyIdentifier === "Alt" is repeatedly sent * but not alternately. * * Once we detect the AltGr key down, then disable KeyBindingManager and set up a keyup * event listener to detect the release of the altGr key so that we can re-enable KeyBindingManager. * When we detect the addition of Ctrl key besides AltGr key, we also quit AltGr mode and re-enable * KeyBindingManager. * * @param {!KeyboardEvent} e keyboard event object */ function _detectAltGrKeyDown(e) { if (brackets.platform !== "win") { return; } if (!_altGrDown) { if (_ctrlDown !== CtrlDownStates.DETECTED_AND_IGNORED && e.ctrlKey && e.keyIdentifier === "Control") { _ctrlDown = CtrlDownStates.DETECTED; } else if (e.repeat && e.ctrlKey && e.keyIdentifier === "Control") { // We get here if the user is holding down left/right Control key. Set it to false // so that we don't misidentify the combination of Ctrl and Alt keys as AltGr key. _ctrlDown = CtrlDownStates.DETECTED_AND_IGNORED; } else if (_ctrlDown === CtrlDownStates.DETECTED && e.altKey && e.ctrlKey && e.keyIdentifier === "Alt" && (e.timeStamp - _lastTimeStamp) < MAX_INTERVAL_FOR_CTRL_ALT_KEYS) { _altGrDown = true; _lastKeyIdentifier = "Alt"; _enabled = false; $(window).on("keyup", _onCtrlUp); } else { // Reset _ctrlDown so that we can start over in detecting the two key events // required for AltGr key. _ctrlDown = CtrlDownStates.NOT_YET_DETECTED; } _lastTimeStamp = e.timeStamp; } else if (e.keyIdentifier === "Control" || e.keyIdentifier === "Alt") { // If the user is NOT holding down AltGr key or is also pressing Ctrl key, // then _lastKeyIdentifier will be the same as keyIdentifier in the current // key event. So we need to quit AltGr mode to re-enable KBM. if (e.altKey && e.ctrlKey && e.keyIdentifier === _lastKeyIdentifier) { _quitAltGrMode(); } else { _lastKeyIdentifier = e.keyIdentifier; } } } /** * @private */ function _reset() { _keyMap = {}; _defaultKeyMap = {}; _customKeyMap = {}; _customKeyMapCache = {}; _commandMap = {}; _globalKeydownHooks = []; _userKeyMapFilePath = brackets.app.getApplicationSupportDirectory() + "/" + KEYMAP_FILENAME; } /** * @private * Initialize an empty keymap as the current keymap. It overwrites the current keymap if there is one. * builds the keyDescriptor string from the given parts * @param {boolean} hasCtrl Is Ctrl key enabled * @param {boolean} hasAlt Is Alt key enabled * @param {boolean} hasShift Is Shift key enabled * @param {string} key The key that's pressed * @return {string} The normalized key descriptor */ function _buildKeyDescriptor(hasMacCtrl, hasCtrl, hasAlt, hasShift, key) { if (!key) { console.log("KeyBindingManager _buildKeyDescriptor() - No key provided!"); return ""; } var keyDescriptor = []; if (hasMacCtrl) { keyDescriptor.push("Ctrl"); } if (hasAlt) { keyDescriptor.push("Alt"); } if (hasShift) { keyDescriptor.push("Shift"); } if (hasCtrl) { // Windows display Ctrl first, Mac displays Command symbol last if (brackets.platform === "mac") { keyDescriptor.push("Cmd"); } else { keyDescriptor.unshift("Ctrl"); } } keyDescriptor.push(key); return keyDescriptor.join("-"); } /** * normalizes the incoming key descriptor so the modifier keys are always specified in the correct order * @param {string} The string for a key descriptor, can be in any order, the result will be Ctrl-Alt-Shift- * @return {string} The normalized key descriptor or null if the descriptor invalid */ function normalizeKeyDescriptorString(origDescriptor) { var hasMacCtrl = false, hasCtrl = false, hasAlt = false, hasShift = false, key = "", error = false; function _compareModifierString(left, right) { if (!left || !right) { return false; } left = left.trim().toLowerCase(); right = right.trim().toLowerCase(); return (left.length > 0 && left === right); } origDescriptor.split("-").forEach(function parseDescriptor(ele, i, arr) { if (_compareModifierString("ctrl", ele)) { if (brackets.platform === "mac") { hasMacCtrl = true; } else { hasCtrl = true; } } else if (_compareModifierString("cmd", ele)) { if (brackets.platform === "mac") { hasCtrl = true; } else { error = true; } } else if (_compareModifierString("alt", ele)) { hasAlt = true; } else if (_compareModifierString("opt", ele)) { if (brackets.platform === "mac") { hasAlt = true; } else { error = true; } } else if (_compareModifierString("shift", ele)) { hasShift = true; } else if (key.length > 0) { console.log("KeyBindingManager normalizeKeyDescriptorString() - Multiple keys defined. Using key: " + key + " from: " + origDescriptor); error = true; } else { key = ele; } }); if (error) { return null; } // Check to see if the binding is for "-". if (key === "" && origDescriptor.search(/^.+--$/) !== -1) { key = "-"; } // '+' char is valid if it's the only key. Keyboard shortcut strings should use // unicode characters (unescaped). Keyboard shortcut display strings may use // unicode escape sequences (e.g. \u20AC euro sign) if ((key.indexOf("+")) >= 0 && (key.length > 1)) { return null; } // Ensure that the first letter of the key name is in upper case and the rest are // in lower case. i.e. 'a' => 'A' and 'up' => 'Up' if (/^[a-z]/i.test(key)) { key = _.capitalize(key.toLowerCase()); } // Also make sure that the second word of PageUp/PageDown has the first letter in upper case. if (/^Page/.test(key)) { key = key.replace(/(up|down)$/, function (match, p1) { return _.capitalize(p1); }); } // No restriction on single character key yet, but other key names are restricted to either // Function keys or those listed in _keyNames array. if (key.length > 1 && !/F\d+/.test(key) && _keyNames.indexOf(key) === -1) { return null; } return _buildKeyDescriptor(hasMacCtrl, hasCtrl, hasAlt, hasShift, key); } /** * @private * Looks for keycodes that have os-inconsistent keys and fixes them. * @param {number} The keycode from the keyboard event. * @param {string} The current best guess at what the key is. * @return {string} If the key is OS-inconsistent, the correct key; otherwise, the original key. **/ function _mapKeycodeToKey(keycode, key) { // If keycode represents one of the digit keys (0-9), then return the corresponding digit // by subtracting KeyEvent.DOM_VK_0 from keycode. ie. [48-57] --> [0-9] if (keycode >= KeyEvent.DOM_VK_0 && keycode <= KeyEvent.DOM_VK_9) { return String(keycode - KeyEvent.DOM_VK_0); // Do the same with the numpad numbers // by subtracting KeyEvent.DOM_VK_NUMPAD0 from keycode. ie. [96-105] --> [0-9] } else if (keycode >= KeyEvent.DOM_VK_NUMPAD0 && keycode <= KeyEvent.DOM_VK_NUMPAD9) { return String(keycode - KeyEvent.DOM_VK_NUMPAD0); } switch (keycode) { case KeyEvent.DOM_VK_SEMICOLON: return ";"; case KeyEvent.DOM_VK_EQUALS: return "="; case KeyEvent.DOM_VK_COMMA: return ","; case KeyEvent.DOM_VK_SUBTRACT: case KeyEvent.DOM_VK_DASH: return "-"; case KeyEvent.DOM_VK_ADD: return "+"; case KeyEvent.DOM_VK_DECIMAL: case KeyEvent.DOM_VK_PERIOD: return "."; case KeyEvent.DOM_VK_DIVIDE: case KeyEvent.DOM_VK_SLASH: return "/"; case KeyEvent.DOM_VK_BACK_QUOTE: return "`"; case KeyEvent.DOM_VK_OPEN_BRACKET: return "["; case KeyEvent.DOM_VK_BACK_SLASH: return "\\"; case KeyEvent.DOM_VK_CLOSE_BRACKET: return "]"; case KeyEvent.DOM_VK_QUOTE: return "'"; default: return key; } } /** * Takes a keyboard event and translates it into a key in a key map */ function _translateKeyboardEvent(event) { var hasMacCtrl = (brackets.platform === "mac") ? (event.ctrlKey) : false, hasCtrl = (brackets.platform !== "mac") ? (event.ctrlKey) : (event.metaKey), hasAlt = (event.altKey), hasShift = (event.shiftKey), key = String.fromCharCode(event.keyCode); //From the W3C, if we can get the KeyboardEvent.keyIdentifier then look here //As that will let us use keys like then function keys "F5" for commands. The //full set of values we can use is here //http://www.w3.org/TR/2007/WD-DOM-Level-3-Events-20071221/keyset.html#KeySet-Set var ident = event.keyIdentifier; if (ident) { if (ident.charAt(0) === "U" && ident.charAt(1) === "+") { //This is a unicode code point like "U+002A", get the 002A and use that key = String.fromCharCode(parseInt(ident.substring(2), 16)); } else { //This is some non-character key, just use the raw identifier key = ident; } } // Translate some keys to their common names if (key === "\t") { key = "Tab"; } else if (key === " ") { key = "Space"; } else if (key === "\b") { key = "Backspace"; } else if (key === "Help") { key = "Insert"; } else if (event.keyCode === KeyEvent.DOM_VK_DELETE) { key = "Delete"; } else { key = _mapKeycodeToKey(event.keyCode, key); } return _buildKeyDescriptor(hasMacCtrl, hasCtrl, hasAlt, hasShift, key); } /** * Convert normalized key representation to display appropriate for platform. * @param {!string} descriptor Normalized key descriptor. * @return {!string} Display/Operating system appropriate string */ function formatKeyDescriptor(descriptor) { var displayStr; if (brackets.platform === "mac") { displayStr = descriptor.replace(/-(?!$)/g, ""); // remove dashes displayStr = displayStr.replace("Ctrl", "\u2303"); // Ctrl > control symbol displayStr = displayStr.replace("Cmd", "\u2318"); // Cmd > command symbol displayStr = displayStr.replace("Shift", "\u21E7"); // Shift > shift symbol displayStr = displayStr.replace("Alt", "\u2325"); // Alt > option symbol } else { displayStr = descriptor.replace("Ctrl", Strings.KEYBOARD_CTRL); displayStr = displayStr.replace("Shift", Strings.KEYBOARD_SHIFT); displayStr = displayStr.replace(/-(?!$)/g, "+"); } displayStr = displayStr.replace("Space", Strings.KEYBOARD_SPACE); displayStr = displayStr.replace("PageUp", Strings.KEYBOARD_PAGE_UP); displayStr = displayStr.replace("PageDown", Strings.KEYBOARD_PAGE_DOWN); displayStr = displayStr.replace("Home", Strings.KEYBOARD_HOME); displayStr = displayStr.replace("End", Strings.KEYBOARD_END); displayStr = displayStr.replace("Ins", Strings.KEYBOARD_INSERT); displayStr = displayStr.replace("Del", Strings.KEYBOARD_DELETE); return displayStr; } /** * @private * @param {string} A normalized key-description string. * @return {boolean} true if the key is already assigned, false otherwise. */ function _isKeyAssigned(key) { return (_keyMap[key] !== undefined); } /** * Remove a key binding from _keymap * * @param {!string} key - a key-description string that may or may not be normalized. * @param {?string} platform - OS from which to remove the binding (all platforms if unspecified) */ function removeBinding(key, platform) { if (!key || ((platform !== null) && (platform !== undefined) && (platform !== brackets.platform))) { return; } var normalizedKey = normalizeKeyDescriptorString(key); if (!normalizedKey) { console.log("Failed to normalize " + key); } else if (_isKeyAssigned(normalizedKey)) { var binding = _keyMap[normalizedKey], command = CommandManager.get(binding.commandID), bindings = _commandMap[binding.commandID]; // delete key binding record delete _keyMap[normalizedKey]; if (bindings) { // delete mapping from command to key binding _commandMap[binding.commandID] = bindings.filter(function (b) { return (b.key !== normalizedKey); }); if (command) { command.trigger("keyBindingRemoved", {key: normalizedKey, displayKey: binding.displayKey}); } } } } /** * @private * * Updates _allCommands array and _defaultKeyMap with the new key binding * if it is not yet in the _allCommands array. _allCommands array is initialized * only in extensionsLoaded event. So any new commands or key bindings added after * that will be updated here. * * @param {{commandID: string, key: string, displayKey:string, explicitPlatform: string}} newBinding */ function _updateCommandAndKeyMaps(newBinding) { if (_allCommands.length === 0) { return; } if (newBinding && newBinding.commandID && _allCommands.indexOf(newBinding.commandID) === -1) { _defaultKeyMap[newBinding.commandID] = _.cloneDeep(newBinding); // Process user key map again to catch any reassignment to all new key bindings added from extensions. _loadUserKeyMap(); } } /** * @private * * @param {string} commandID * @param {string|{{key: string, displayKey: string}}} keyBinding - a single shortcut. * @param {?string} platform * - "all" indicates all platforms, not overridable * - undefined indicates all platforms, overridden by platform-specific binding * @param {boolean=} userBindings true if adding a user key binding or undefined otherwise. * @return {?{key: string, displayKey:String}} Returns a record for valid key bindings. * Returns null when key binding platform does not match, binding does not normalize, * or is already assigned. */ function _addBinding(commandID, keyBinding, platform, userBindings) { var key, result = null, normalized, normalizedDisplay, explicitPlatform = keyBinding.platform || platform, targetPlatform, command, bindingsToDelete = [], existing; // For platform: "all", use explicit current platform if (explicitPlatform && explicitPlatform !== "all") { targetPlatform = explicitPlatform; } else { targetPlatform = brackets.platform; } // Skip if the key binding is not for this platform. if (explicitPlatform === "mac" && brackets.platform !== "mac") { return null; } // if the request does not specify an explicit platform, and we're // currently on a mac, then replace Ctrl with Cmd. key = (keyBinding.key) || keyBinding; if (brackets.platform === "mac" && (explicitPlatform === undefined || explicitPlatform === "all")) { key = key.replace("Ctrl", "Cmd"); if (keyBinding.displayKey !== undefined) { keyBinding.displayKey = keyBinding.displayKey.replace("Ctrl", "Cmd"); } } normalized = normalizeKeyDescriptorString(key); // skip if the key binding is invalid if (!normalized) { console.error("Unable to parse key binding " + key + ". Permitted modifiers: Ctrl, Cmd, Alt, Opt, Shift; separated by '-' (not '+')."); return null; } // check for duplicate key bindings existing = _keyMap[normalized]; // for cross-platform compatibility if (exports.useWindowsCompatibleBindings) { // windows-only key bindings are used as the default binding // only if a default binding wasn't already defined if (explicitPlatform === "win") { // search for a generic or platform-specific binding if it // already exists if (existing && (!existing.explicitPlatform || existing.explicitPlatform === brackets.platform || existing.explicitPlatform === "all")) { // do not clobber existing binding with windows-only binding return null; } // target this windows binding for the current platform targetPlatform = brackets.platform; } } // skip if this binding doesn't match the current platform if (targetPlatform !== brackets.platform) { return null; } // skip if the key is already assigned if (existing) { if (!existing.explicitPlatform && explicitPlatform) { // remove the the generic binding to replace with this new platform-specific binding removeBinding(normalized); existing = false; } } // delete existing bindings when // (1) replacing a windows-compatible binding with a generic or // platform-specific binding // (2) replacing a generic binding with a platform-specific binding var existingBindings = _commandMap[commandID] || [], isWindowsCompatible, isReplaceGeneric, ignoreGeneric; existingBindings.forEach(function (binding) { // remove windows-only bindings in _commandMap isWindowsCompatible = exports.useWindowsCompatibleBindings && binding.explicitPlatform === "win"; // remove existing generic binding isReplaceGeneric = !binding.explicitPlatform && explicitPlatform; if (isWindowsCompatible || isReplaceGeneric) { bindingsToDelete.push(binding); } else { // existing binding is platform-specific and the requested binding is generic ignoreGeneric = binding.explicitPlatform && !explicitPlatform; } }); if (ignoreGeneric) { // explicit command binding overrides this one return null; } if (existing) { // do not re-assign a key binding console.error("Cannot assign " + normalized + " to " + commandID + ". It is already assigned to " + _keyMap[normalized].commandID); return null; } // remove generic or windows-compatible bindings bindingsToDelete.forEach(function (binding) { removeBinding(binding.key); }); // optional display-friendly string (e.g. CMD-+ instead of CMD-=) normalizedDisplay = (keyBinding.displayKey) ? normalizeKeyDescriptorString(keyBinding.displayKey) : normalized; // 1-to-many commandID mapping to key binding if (!_commandMap[commandID]) { _commandMap[commandID] = []; } result = { key : normalized, displayKey : normalizedDisplay, explicitPlatform : explicitPlatform }; _commandMap[commandID].push(result); // 1-to-1 key binding to commandID _keyMap[normalized] = { commandID : commandID, key : normalized, displayKey : normalizedDisplay, explicitPlatform : explicitPlatform }; if (!userBindings) { _updateCommandAndKeyMaps(_keyMap[normalized]); } // notify listeners command = CommandManager.get(commandID); if (command) { command.trigger("keyBindingAdded", result); } return result; } /** * Returns a copy of the current key map. If the optional 'defaults' parameter is true, * then a copy of the default key map is returned. * @param {boolean=} defaults true if the caller wants a copy of the default key map. * Otherwise, the current active key map is returned. * @return {!Object.} */ function getKeymap(defaults) { return $.extend({}, defaults ? _defaultKeyMap : _keyMap); } /** * Process the keybinding for the current key. * * @param {string} A key-description string. * @return {boolean} true if the key was processed, false otherwise */ function _handleKey(key) { if (_enabled && _keyMap[key]) { // The execute() function returns a promise because some commands are async. // Generally, commands decide whether they can run or not synchronously, // and reject immediately, so we can test for that synchronously. var promise = CommandManager.execute(_keyMap[key].commandID); return (promise.state() !== "rejected"); } return false; } /** * @private * * Sort objects by platform property. Objects with a platform property come * before objects without a platform property. */ function _sortByPlatform(a, b) { var a1 = (a.platform) ? 1 : 0, b1 = (b.platform) ? 1 : 0; return b1 - a1; } /** * Add one or more key bindings to a particular Command. * * @param {!string | Command} command - A command ID or command object * @param {?({key: string, displayKey: string}|Array.<{key: string, displayKey: string, platform: string}>)} keyBindings * A single key binding or an array of keybindings. Example: * "Shift-Cmd-F". Mac and Win key equivalents are automatically * mapped to each other. Use displayKey property to display a different * string (e.g. "CMD+" instead of "CMD="). * @param {?string} platform The target OS of the keyBindings either * "mac", "win" or "linux". If undefined, all platforms not explicitly * defined will use the key binding. * NOTE: If platform is not specified, Ctrl will be replaced by Cmd for "mac" platform * @return {{key: string, displayKey:String}|Array.<{key: string, displayKey:String}>} * Returns record(s) for valid key binding(s) */ function addBinding(command, keyBindings, platform) { var commandID = "", results; if (!command) { console.error("addBinding(): missing required parameter: command"); return; } if (!keyBindings) { return; } if (typeof (command) === "string") { commandID = command; } else { commandID = command.getID(); } if (Array.isArray(keyBindings)) { var keyBinding; results = []; // process platform-specific bindings first keyBindings.sort(_sortByPlatform); keyBindings.forEach(function addSingleBinding(keyBindingRequest) { // attempt to add keybinding keyBinding = _addBinding(commandID, keyBindingRequest, keyBindingRequest.platform); if (keyBinding) { results.push(keyBinding); } }); } else { results = _addBinding(commandID, keyBindings, platform); } return results; } /** * Retrieve key bindings currently associated with a command * * @param {!string | Command} command - A command ID or command object * @return {!Array.<{{key: string, displayKey: string}}>} An array of associated key bindings. */ function getKeyBindings(command) { var bindings = [], commandID = ""; if (!command) { console.error("getKeyBindings(): missing required parameter: command"); return []; } if (typeof (command) === "string") { commandID = command; } else { commandID = command.getID(); } bindings = _commandMap[commandID]; return bindings || []; } /** * Adds default key bindings when commands are registered to CommandManager * @param {$.Event} event jQuery event * @param {Command} command Newly registered command */ function _handleCommandRegistered(event, command) { var commandId = command.getID(), defaults = KeyboardPrefs[commandId]; if (defaults) { addBinding(commandId, defaults); } } /** * Adds a global keydown hook that gets first crack at keydown events * before standard keybindings do. This is intended for use by modal or * semi-modal UI elements like dialogs or the code hint list that should * execute before normal command bindings are run. * * The hook is passed one parameter, the original keyboard event. If the * hook handles the event (or wants to block other global hooks from * handling the event), it should return true. Note that this will *only* * stop other global hooks and KeyBindingManager from handling the * event; to prevent further event propagation, you will need to call * stopPropagation(), stopImmediatePropagation(), and/or preventDefault() * as usual. * * Multiple keydown hooks can be registered, and are executed in order, * most-recently-added first. * * (We have to have a special API for this because (1) handlers are normally * called in least-recently-added order, and we want most-recently-added; * (2) native DOM events don't have a way for us to find out if * stopImmediatePropagation()/stopPropagation() has been called on the * event, so we have to have some other way for one of the hooks to * indicate that it wants to block the other hooks from running.) * * @param {function(Event): boolean} hook The global hook to add. */ function addGlobalKeydownHook(hook) { _globalKeydownHooks.push(hook); } /** * Removes a global keydown hook added by `addGlobalKeydownHook`. * Does not need to be the most recently added hook. * * @param {function(Event): boolean} hook The global hook to remove. */ function removeGlobalKeydownHook(hook) { var index = _globalKeydownHooks.indexOf(hook); if (index !== -1) { _globalKeydownHooks.splice(index, 1); } } /** * Handles a given keydown event, checking global hooks first before * deciding to handle it ourselves. * @param {Event} The keydown event to handle. */ function _handleKeyEvent(event) { var i, handled = false; for (i = _globalKeydownHooks.length - 1; i >= 0; i--) { if (_globalKeydownHooks[i](event)) { handled = true; break; } } _detectAltGrKeyDown(event); if (!handled && _handleKey(_translateKeyboardEvent(event))) { event.stopPropagation(); event.preventDefault(); } } AppInit.htmlReady(function () { // Install keydown event listener. window.document.body.addEventListener( "keydown", _handleKeyEvent, true ); exports.useWindowsCompatibleBindings = (brackets.platform !== "mac") && (brackets.platform !== "win"); }); /** * @private * Displays an error dialog and also opens the user key map file for editing only if * the error is not the loading file error. * * @param {?string} err Error type returned from JSON parser or open file operation * @param {string=} message Error message to be displayed in the dialog */ function _showErrorsAndOpenKeyMap(err, message) { // Asynchronously loading Dialogs module to avoid the circular dependency require(["widgets/Dialogs"], function (Dialogs) { var errorMessage = Strings.ERROR_KEYMAP_CORRUPT; if (err === FileSystemError.UNSUPPORTED_ENCODING) { errorMessage = Strings.ERROR_LOADING_KEYMAP; } else if (message) { errorMessage = message; } Dialogs.showModalDialog( DefaultDialogs.DIALOG_ID_ERROR, Strings.ERROR_KEYMAP_TITLE, errorMessage ) .done(function () { if (err !== FileSystemError.UNSUPPORTED_ENCODING) { CommandManager.execute(Commands.FILE_OPEN_KEYMAP); } }); }); } /** * @private * * Checks whether the given command ID is a special command that the user can't bind * to another shortcut. * @param {!string} commandID A string referring to a specific command * @return {boolean} true if normalizedKey is a special command, false otherwise. */ function _isSpecialCommand(commandID) { if (brackets.platform === "mac" && commandID === "file.quit") { return true; } return (_specialCommands.indexOf(commandID) > -1); } /** * @private * * Checks whether the given key combination is a shortcut of a special command * or a Mac system command that the user can't reassign to another command. * @param {!string} normalizedKey A key combination string used for a keyboard shortcut * @return {boolean} true if normalizedKey is a restricted shortcut, false otherwise. */ function _isReservedShortcuts(normalizedKey) { if (!normalizedKey) { return false; } if (_reservedShortcuts.indexOf(normalizedKey) > -1 || _reservedShortcuts.indexOf(normalizedKey.replace("Cmd", "Ctrl")) > -1) { return true; } if (brackets.platform === "mac" && _macReservedShortcuts.indexOf(normalizedKey) > -1) { return true; } return false; } /** * @private * * Creates a bullet list item for any item in the given list. * @param {Array.} list An array of strings to be converted into a * message string with a bullet list. * @return {string} the html text version of the list */ function _getBulletList(list) { var message = "
      "; list.forEach(function (info) { message += "
    • " + info + "
    • "; }); message += "
    "; return message; } /** * @private * * Gets the corresponding unicode symbol of an arrow key for display in the menu. * @param {string} key The non-modifier key used in the shortcut. It does not need to be normalized. * @return {string} An empty string if key is not one of those we want to show with the unicode symbol. * Otherwise, the corresponding unicode symbol is returned. */ function _getDisplayKey(key) { var displayKey = "", match = key ? key.match(/(Up|Down|Left|Right|\-)$/i) : null; if (match && !/Page(Up|Down)/.test(key)) { displayKey = key.substr(0, match.index) + _displayKeyMap[match[0].toLowerCase()]; } return displayKey; } /** * @private * * Applies each user key binding to all the affected commands and updates _keyMap. * Shows errors in a dialog and then opens the user key map file if any of the following * is detected while applying the user key bindings. * - A key binding is attempting to modify a special command. * - A key binding is attempting to assign a shortcut of a special command to another one. * - Multiple key bindings are specified for the same command ID. * - The same key combination is listed for multiple key bindings. * - A key binding has any invalid key syntax. * - A key binding is referring to a non-existent command ID. */ function _applyUserKeyBindings() { var remappedCommands = [], remappedKeys = [], restrictedCommands = [], restrictedKeys = [], invalidKeys = [], invalidCommands = [], multipleKeys = [], duplicateBindings = [], errorMessage = ""; _.forEach(_customKeyMap, function (commandID, key) { var normalizedKey = normalizeKeyDescriptorString(key), existingBindings = _commandMap[commandID] || []; // Skip this since we don't allow user to update key binding of a special // command like cut, copy, paste, undo, redo and select all. if (_isSpecialCommand(commandID)) { restrictedCommands.push(commandID); return; } // Skip this since we don't allow user to update a shortcut used in // a special command or any Mac system command. if (_isReservedShortcuts(normalizedKey)) { restrictedKeys.push(key); return; } // Skip this if the key is invalid. if (!normalizedKey) { invalidKeys.push(key); return; } if (_isKeyAssigned(normalizedKey)) { if (remappedKeys.indexOf(normalizedKey) !== -1) { // JSON parser already removed all the duplicates that have the exact // same case or order in their keys. So we're only detecting duplicate // bindings that have different orders or different cases used in the key. duplicateBindings.push(key); return; } // The same key binding already exists, so skip this. if (_keyMap[normalizedKey].commandID === commandID) { // Still need to add it to the remappedCommands so that // we can detect any duplicate later on. remappedCommands.push(commandID); return; } removeBinding(normalizedKey); } if (remappedKeys.indexOf(normalizedKey) === -1) { remappedKeys.push(normalizedKey); } // Remove another key binding if the new key binding is for a command // that has a different key binding. e.g. "Ctrl-W": "edit.selectLine" // requires us to remove "Ctrl-W" from "file.close" command, but we // also need to remove "Ctrl-L" from "edit.selectLine". if (existingBindings.length) { existingBindings.forEach(function (binding) { removeBinding(binding.key); }); } if (commandID) { if (_allCommands.indexOf(commandID) !== -1) { if (remappedCommands.indexOf(commandID) === -1) { var keybinding = { key: normalizedKey }; keybinding.displayKey = _getDisplayKey(normalizedKey); _addBinding(commandID, keybinding.displayKey ? keybinding : normalizedKey, brackets.platform, true); remappedCommands.push(commandID); } else { multipleKeys.push(commandID); } } else { invalidCommands.push(commandID); } } }); if (restrictedCommands.length) { errorMessage = StringUtils.format(Strings.ERROR_RESTRICTED_COMMANDS, _getBulletList(restrictedCommands)); } if (restrictedKeys.length) { errorMessage += StringUtils.format(Strings.ERROR_RESTRICTED_SHORTCUTS, _getBulletList(restrictedKeys)); } if (multipleKeys.length) { errorMessage += StringUtils.format(Strings.ERROR_MULTIPLE_SHORTCUTS, _getBulletList(multipleKeys)); } if (duplicateBindings.length) { errorMessage += StringUtils.format(Strings.ERROR_DUPLICATE_SHORTCUTS, _getBulletList(duplicateBindings)); } if (invalidKeys.length) { errorMessage += StringUtils.format(Strings.ERROR_INVALID_SHORTCUTS, _getBulletList(invalidKeys)); } if (invalidCommands.length) { errorMessage += StringUtils.format(Strings.ERROR_NONEXISTENT_COMMANDS, _getBulletList(invalidCommands)); } if (_showErrors && errorMessage) { _showErrorsAndOpenKeyMap("", errorMessage); } } /** * @private * * Restores the default key bindings for all the commands that are modified by each key binding * specified in _customKeyMapCache (old version) but no longer specified in _customKeyMap (new version). */ function _undoPriorUserKeyBindings() { _.forEach(_customKeyMapCache, function (commandID, key) { var normalizedKey = normalizeKeyDescriptorString(key), defaults = _.find(_.toArray(_defaultKeyMap), { "commandID": commandID }), defaultCommand = _defaultKeyMap[normalizedKey]; // We didn't modified this before, so skip it. if (_isSpecialCommand(commandID) || _isReservedShortcuts(normalizedKey)) { return; } if (_isKeyAssigned(normalizedKey) && _customKeyMap[key] !== commandID && _customKeyMap[normalizedKey] !== commandID) { // Unassign the key from any command. e.g. "Cmd-W": "file.open" in _customKeyMapCache // will require us to remove Cmd-W shortcut from file.open command. removeBinding(normalizedKey); } // Reassign the default key binding. e.g. "Cmd-W": "file.open" in _customKeyMapCache // will require us to reassign Cmd-O shortcut to file.open command. if (defaults) { addBinding(commandID, defaults, brackets.platform); } // Reassign the default key binding of the previously modified command. // e.g. "Cmd-W": "file.open" in _customKeyMapCache will require us to reassign Cmd-W // shortcut to file.close command. if (defaultCommand && defaultCommand.key) { addBinding(defaultCommand.commandID, defaultCommand.key, brackets.platform); } }); } /** * @private * * Gets the full file path to the user key map file. In testing environment * a different file path is returned so that running integration tests won't * pop up the error dialog showing the errors from the actual user key map file. * * @return {string} full file path to the user key map file. */ function _getUserKeyMapFilePath() { if (window.isBracketsTestWindow) { return brackets.app.getApplicationSupportDirectory() + "/_test_/" + KEYMAP_FILENAME; } return _userKeyMapFilePath; } /** * @private * * Reads in the user key map file and parses its content into JSON. * Returns the user key bindings if JSON has "overrides". * Otherwise, returns an empty object or an error if the file * cannot be parsed or loaded. * * @return {$.Promise} a jQuery promise that will be resolved with the JSON * object if the user key map file has "overrides" property or an empty JSON. * If the key map file cannot be read or cannot be parsed by the JSON parser, * then the promise is rejected with an error. */ function _readUserKeyMap() { var file = FileSystem.getFileForPath(_getUserKeyMapFilePath()), result = new $.Deferred(); file.exists(function (err, doesExist) { if (doesExist) { FileUtils.readAsText(file) .done(function (text) { var keyMap = {}; try { if (text) { var json = JSON.parse(text); // If no overrides, return an empty key map. result.resolve((json && json.overrides) || keyMap); } else { // The file is empty, so return an empty key map. result.resolve(keyMap); } } catch (err) { // Cannot parse the text read from the key map file. result.reject(err); } }) .fail(function (err) { // Key map file cannot be loaded. result.reject(err); }); } else { // Just resolve if no user key map file result.resolve(); } }); return result.promise(); } /** * @private * * Reads in the user key bindings and updates the key map with each user key * binding by removing the existing one assigned to each key and adding * new one for the specified command id. Shows errors and opens the user * key map file if it cannot be parsed. * * This function is wrapped with debounce so that its execution is always delayed * by 200 ms. The delay is required because when this function is called some * extensions may still be adding some commands and their key bindings asychronously. */ _loadUserKeyMap = _.debounce(function () { _readUserKeyMap() .then(function (keyMap) { // Some extensions may add a new command without any key binding. So // we always have to get all commands again to ensure that we also have // those from any extensions installed during the current session. _allCommands = CommandManager.getAll(); _customKeyMapCache = _.cloneDeep(_customKeyMap); _customKeyMap = keyMap; _undoPriorUserKeyBindings(); _applyUserKeyBindings(); }, function (err) { _showErrorsAndOpenKeyMap(err); }); }, 200); /** * @private * * Opens the existing key map file or creates a new one with default content * if it does not exist. */ function _openUserKeyMap() { var userKeyMapPath = _getUserKeyMapFilePath(), file = FileSystem.getFileForPath(userKeyMapPath); file.exists(function (err, doesExist) { if (doesExist) { CommandManager.execute(Commands.FILE_OPEN, { fullPath: userKeyMapPath }); } else { var defaultContent = "{\n \"documentation\": \"https://github.com/adobe/brackets/wiki/User-Key-Bindings\"," + "\n \"overrides\": {" + "\n \n }\n}\n"; FileUtils.writeText(file, defaultContent, true) .done(function () { CommandManager.execute(Commands.FILE_OPEN, { fullPath: userKeyMapPath }); }); } }); } // Due to circular dependencies, not safe to call on() directly EventDispatcher.on_duringInit(CommandManager, "commandRegistered", _handleCommandRegistered); CommandManager.register(Strings.CMD_OPEN_KEYMAP, Commands.FILE_OPEN_KEYMAP, _openUserKeyMap); // Asynchronously loading DocumentManager to avoid the circular dependency require(["document/DocumentManager"], function (DocumentManager) { DocumentManager.on("documentSaved", function checkKeyMapUpdates(e, doc) { if (doc && doc.file.fullPath === _userKeyMapFilePath) { _loadUserKeyMap(); } }); }); /** * @private * * Initializes _allCommands array and _defaultKeyMap so that we can use them for * detecting non-existent commands and restoring the original key binding. */ function _initCommandAndKeyMaps() { _allCommands = CommandManager.getAll(); // Keep a copy of the default key bindings before loading user key bindings. _defaultKeyMap = _.cloneDeep(_keyMap); } /** * @private * * Sets the full file path to the user key map file. Only used by unit tests * to load a test file instead of the actual user key map file. * * @param {string} fullPath file path to the user key map file. */ function _setUserKeyMapFilePath(fullPath) { _userKeyMapFilePath = fullPath; } AppInit.extensionsLoaded(function () { var params = new UrlParams(); params.parse(); if (params.get("reloadWithoutUserExts") === "true") { _showErrors = false; } _initCommandAndKeyMaps(); _loadUserKeyMap(); }); // unit test only exports._reset = _reset; exports._setUserKeyMapFilePath = _setUserKeyMapFilePath; exports._getDisplayKey = _getDisplayKey; exports._loadUserKeyMap = _loadUserKeyMap; exports._initCommandAndKeyMaps = _initCommandAndKeyMaps; exports._onCtrlUp = _onCtrlUp; // Define public API exports.getKeymap = getKeymap; exports.addBinding = addBinding; exports.removeBinding = removeBinding; exports.formatKeyDescriptor = formatKeyDescriptor; exports.getKeyBindings = getKeyBindings; exports.addGlobalKeydownHook = addGlobalKeydownHook; exports.removeGlobalKeydownHook = removeGlobalKeydownHook; /** * Use windows-specific bindings if no other are found (e.g. Linux). * Core Brackets modules that use key bindings should always define at * least a generic keybinding that is applied for all platforms. This * setting effectively creates a compatibility mode for third party * extensions that define explicit key bindings for Windows and Mac, but * not Linux. */ exports.useWindowsCompatibleBindings = false; // For unit testing only exports._handleKey = _handleKey; exports._handleKeyEvent = _handleKeyEvent; }); ================================================ FILE: src/command/Menus.js ================================================ /* * Copyright (c) 2012 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ define(function (require, exports, module) { "use strict"; var _ = require("thirdparty/lodash"); // Load dependent modules var Commands = require("command/Commands"), EventDispatcher = require("utils/EventDispatcher"), KeyBindingManager = require("command/KeyBindingManager"), StringUtils = require("utils/StringUtils"), CommandManager = require("command/CommandManager"), PopUpManager = require("widgets/PopUpManager"), ViewUtils = require("utils/ViewUtils"), DeprecationWarning = require("utils/DeprecationWarning"); // make sure the global brackets variable is loaded require("utils/Global"); /** * Brackets Application Menu Constants * @enum {string} */ var AppMenuBar = { FILE_MENU : "file-menu", EDIT_MENU : "edit-menu", FIND_MENU : "find-menu", VIEW_MENU : "view-menu", NAVIGATE_MENU : "navigate-menu", HELP_MENU : "help-menu" }; /** * Brackets Context Menu Constants * @enum {string} */ var ContextMenuIds = { EDITOR_MENU: "editor-context-menu", INLINE_EDITOR_MENU: "inline-editor-context-menu", PROJECT_MENU: "project-context-menu", WORKING_SET_CONTEXT_MENU: "workingset-context-menu", WORKING_SET_CONFIG_MENU: "workingset-configuration-menu", SPLITVIEW_MENU: "splitview-menu" }; /** * Brackets Application Menu Section Constants * It is preferred that plug-ins specify the location of new MenuItems * in terms of a menu section rather than a specific MenuItem. This provides * looser coupling to Bracket's internal MenuItems and makes menu organization * more semantic. * Use these constants as the "relativeID" parameter when calling addMenuItem() and * specify a position of FIRST_IN_SECTION or LAST_IN_SECTION. * * Menu sections are denoted by dividers or the beginning/end of a menu */ var MenuSection = { // Menu Section Command ID to mark the section FILE_OPEN_CLOSE_COMMANDS: {sectionMarker: Commands.FILE_NEW}, FILE_SAVE_COMMANDS: {sectionMarker: Commands.FILE_SAVE}, FILE_LIVE: {sectionMarker: Commands.FILE_LIVE_FILE_PREVIEW}, FILE_EXTENSION_MANAGER: {sectionMarker: Commands.FILE_EXTENSION_MANAGER}, EDIT_UNDO_REDO_COMMANDS: {sectionMarker: Commands.EDIT_UNDO}, EDIT_TEXT_COMMANDS: {sectionMarker: Commands.EDIT_CUT}, EDIT_SELECTION_COMMANDS: {sectionMarker: Commands.EDIT_SELECT_ALL}, EDIT_MODIFY_SELECTION: {sectionMarker: Commands.EDIT_INDENT}, EDIT_COMMENT_SELECTION: {sectionMarker: Commands.EDIT_LINE_COMMENT}, EDIT_CODE_HINTS_COMMANDS: {sectionMarker: Commands.SHOW_CODE_HINTS}, EDIT_TOGGLE_OPTIONS: {sectionMarker: Commands.TOGGLE_CLOSE_BRACKETS}, FIND_FIND_COMMANDS: {sectionMarker: Commands.CMD_FIND}, FIND_FIND_IN_COMMANDS: {sectionMarker: Commands.CMD_FIND_IN_FILES}, FIND_REPLACE_COMMANDS: {sectionMarker: Commands.CMD_REPLACE}, VIEW_HIDESHOW_COMMANDS: {sectionMarker: Commands.VIEW_HIDE_SIDEBAR}, VIEW_FONTSIZE_COMMANDS: {sectionMarker: Commands.VIEW_INCREASE_FONT_SIZE}, VIEW_TOGGLE_OPTIONS: {sectionMarker: Commands.TOGGLE_ACTIVE_LINE}, NAVIGATE_GOTO_COMMANDS: {sectionMarker: Commands.NAVIGATE_QUICK_OPEN}, NAVIGATE_DOCUMENTS_COMMANDS: {sectionMarker: Commands.NAVIGATE_NEXT_DOC}, NAVIGATE_OS_COMMANDS: {sectionMarker: Commands.NAVIGATE_SHOW_IN_FILE_TREE}, NAVIGATE_QUICK_EDIT_COMMANDS: {sectionMarker: Commands.TOGGLE_QUICK_EDIT}, NAVIGATE_QUICK_DOCS_COMMANDS: {sectionMarker: Commands.TOGGLE_QUICK_DOCS} }; /** * Insertion position constants * Used by addMenu(), addMenuItem(), and addSubMenu() to * specify the relative position of a newly created menu object * @enum {string} */ var BEFORE = "before", AFTER = "after", FIRST = "first", LAST = "last", FIRST_IN_SECTION = "firstInSection", LAST_IN_SECTION = "lastInSection"; /** * Other constants */ var DIVIDER = "---"; var SUBMENU = "SUBMENU"; /** * Error Codes from Brackets Shell * @enum {number} */ var NO_ERROR = 0, ERR_UNKNOWN = 1, ERR_INVALID_PARAMS = 2, ERR_NOT_FOUND = 3; /** * Maps menuID's to Menu objects * @type {Object.} */ var menuMap = {}; /** * Maps contextMenuID's to ContextMenu objects * @type {Object.} */ var contextMenuMap = {}; /** * Maps menuItemID's to MenuItem objects * @type {Object.} */ var menuItemMap = {}; /** * Retrieves the Menu object for the corresponding id. * @param {string} id * @return {Menu} */ function getMenu(id) { return menuMap[id]; } /** * Retrieves the map of all Menu objects. * @return {Object.} */ function getAllMenus() { return menuMap; } /** * Retrieves the ContextMenu object for the corresponding id. * @param {string} id * @return {ContextMenu} */ function getContextMenu(id) { return contextMenuMap[id]; } /** * Removes the attached event listeners from the corresponding object. * @param {ManuItem} menuItem */ function removeMenuItemEventListeners(menuItem) { menuItem._command .off("enabledStateChange", menuItem._enabledChanged) .off("checkedStateChange", menuItem._checkedChanged) .off("nameChange", menuItem._nameChanged) .off("keyBindingAdded", menuItem._keyBindingAdded) .off("keyBindingRemoved", menuItem._keyBindingRemoved); } /** * Check whether a ContextMenu exists for the given id. * @param {string} id * @return {boolean} */ function _isContextMenu(id) { return !!getContextMenu(id); } function _isHTMLMenu(id) { return (!brackets.nativeMenus || _isContextMenu(id)); } /** * Retrieves the MenuItem object for the corresponding id. * @param {string} id * @return {MenuItem} */ function getMenuItem(id) { return menuItemMap[id]; } function _getHTMLMenu(id) { return $("#" + StringUtils.jQueryIdEscape(id)).get(0); } function _getHTMLMenuItem(id) { return $("#" + StringUtils.jQueryIdEscape(id)).get(0); } function _addKeyBindingToMenuItem($menuItem, key, displayKey) { var $shortcut = $menuItem.find(".menu-shortcut"); if ($shortcut.length === 0) { $shortcut = $(""); $menuItem.append($shortcut); } $shortcut.data("key", key); $shortcut.text(KeyBindingManager.formatKeyDescriptor(displayKey)); } function _addExistingKeyBinding(menuItem) { var bindings = KeyBindingManager.getKeyBindings(menuItem.getCommand().getID()), binding = null; if (bindings.length > 0) { // add the latest key binding binding = bindings[bindings.length - 1]; _addKeyBindingToMenuItem($(_getHTMLMenuItem(menuItem.id)), binding.key, binding.displayKey); } return binding; } var _menuDividerIDCount = 1; function _getNextMenuItemDividerID() { return "brackets-menuDivider-" + _menuDividerIDCount++; } // Help function for inserting elements into a list function _insertInList($list, $element, position, $relativeElement) { // Determine where to insert. Default is LAST. var inserted = false; if (position) { // Adjust relative position for menu section positions since $relativeElement // has already been resolved by _getRelativeMenuItem() to a menuItem if (position === FIRST_IN_SECTION) { position = BEFORE; } else if (position === LAST_IN_SECTION) { position = AFTER; } if (position === FIRST) { $list.prepend($element); inserted = true; } else if ($relativeElement && $relativeElement.length > 0) { if (position === AFTER) { $relativeElement.after($element); inserted = true; } else if (position === BEFORE) { $relativeElement.before($element); inserted = true; } } } // Default to LAST if (!inserted) { $list.append($element); } } /** * MenuItem represents a single menu item that executes a Command or a menu divider. MenuItems * may have a sub-menu. A MenuItem may correspond to an HTML-based * menu item or a native menu item if Brackets is running in a native application shell * * Since MenuItems may have a native implementation clients should create MenuItems through * addMenuItem() and should NOT construct a MenuItem object directly. * Clients should also not access HTML content of a menu directly and instead use * the MenuItem API to query and modify menus items. * * MenuItems are views on to Command objects so modify the underlying Command to modify the * name, enabled, and checked state of a MenuItem. The MenuItem will update automatically * * @constructor * @private * * @param {string} id * @param {string|Command} command - the Command this MenuItem will reflect. * Use DIVIDER to specify a menu divider */ function MenuItem(id, command) { this.id = id; this.isDivider = (command === DIVIDER); this.isNative = false; if (!this.isDivider && command !== SUBMENU) { // Bind event handlers this._enabledChanged = this._enabledChanged.bind(this); this._checkedChanged = this._checkedChanged.bind(this); this._nameChanged = this._nameChanged.bind(this); this._keyBindingAdded = this._keyBindingAdded.bind(this); this._keyBindingRemoved = this._keyBindingRemoved.bind(this); this._command = command; this._command .on("enabledStateChange", this._enabledChanged) .on("checkedStateChange", this._checkedChanged) .on("nameChange", this._nameChanged) .on("keyBindingAdded", this._keyBindingAdded) .on("keyBindingRemoved", this._keyBindingRemoved); } } /** * Menu represents a top-level menu in the menu bar. A Menu may correspond to an HTML-based * menu or a native menu if Brackets is running in a native application shell. * * Since menus may have a native implementation clients should create Menus through * addMenu() and should NOT construct a Menu object directly. * Clients should also not access HTML content of a menu directly and instead use * the Menu API to query and modify menus. * * @constructor * @private * * @param {string} id */ function Menu(id) { this.id = id; } Menu.prototype._getMenuItemId = function (commandId) { return (this.id + "-" + commandId); }; /** * Determine MenuItem in this Menu, that has the specified command * * @param {Command} command - the command to search for. * @return {?HTMLLIElement} menu item list element */ Menu.prototype._getMenuItemForCommand = function (command) { if (!command) { return null; } var foundMenuItem = menuItemMap[this._getMenuItemId(command.getID())]; if (!foundMenuItem) { return null; } return $(_getHTMLMenuItem(foundMenuItem.id)).closest("li"); }; /** * Determine relative MenuItem * * @param {?string} relativeID - id of command (future: sub-menu). * @param {?string} position - only needed when relativeID is a MenuSection * @return {?HTMLLIElement} menu item list element */ Menu.prototype._getRelativeMenuItem = function (relativeID, position) { var $relativeElement; if (relativeID) { if (position === FIRST_IN_SECTION || position === LAST_IN_SECTION) { if (!relativeID.hasOwnProperty("sectionMarker")) { console.error("Bad Parameter in _getRelativeMenuItem(): relativeID must be a MenuSection when position refers to a menu section"); return null; } // Determine the $relativeElement by traversing the sibling list and // stop at the first divider found // TODO: simplify using nextUntil()/prevUntil() var $sectionMarker = this._getMenuItemForCommand(CommandManager.get(relativeID.sectionMarker)); if (!$sectionMarker) { console.error("_getRelativeMenuItem(): MenuSection " + relativeID.sectionMarker + " not found in Menu " + this.id); return null; } var $listElem = $sectionMarker; $relativeElement = $listElem; while (true) { $listElem = (position === FIRST_IN_SECTION ? $listElem.prev() : $listElem.next()); if ($listElem.length === 0) { break; } else if ($listElem.find(".divider").length > 0) { break; } else { $relativeElement = $listElem; } } } else { if (relativeID.hasOwnProperty("sectionMarker")) { console.error("Bad Parameter in _getRelativeMenuItem(): if relativeID is a MenuSection, position must be FIRST_IN_SECTION or LAST_IN_SECTION"); return null; } // handle FIRST, LAST, BEFORE, & AFTER var command = CommandManager.get(relativeID); if (command) { // Lookup Command for this Command id // Find MenuItem that has this command $relativeElement = this._getMenuItemForCommand(command); } if (!$relativeElement) { console.error("_getRelativeMenuItem(): MenuItem with Command id " + relativeID + " not found in Menu " + this.id); return null; } } return $relativeElement; } else if (position && position !== FIRST && position !== LAST) { console.error("Bad Parameter in _getRelativeMenuItem(): relative position specified with no relativeID"); return null; } return $relativeElement; }; /** * Removes the specified menu item from this Menu. Key bindings are unaffected; use KeyBindingManager * directly to remove key bindings if desired. * * @param {!string | Command} command - command the menu would execute if we weren't deleting it. */ Menu.prototype.removeMenuItem = function (command) { var menuItemID, commandID; if (!command) { console.error("removeMenuItem(): missing required parameters: command"); return; } if (typeof (command) === "string") { var commandObj = CommandManager.get(command); if (!commandObj) { console.error("removeMenuItem(): command not found: " + command); return; } commandID = command; } else { commandID = command.getID(); } menuItemID = this._getMenuItemId(commandID); var menuItem = getMenuItem(menuItemID); removeMenuItemEventListeners(menuItem); if (_isHTMLMenu(this.id)) { // Targeting parent to get the menu item and the
  • that contains it $(_getHTMLMenuItem(menuItemID)).parent().remove(); } else { brackets.app.removeMenuItem(commandID, function (err) { if (err) { console.error("removeMenuItem() -- command not found: " + commandID + " (error: " + err + ")"); } }); } delete menuItemMap[menuItemID]; }; /** * Removes the specified menu divider from this Menu. * * @param {!string} menuItemID - the menu item id of the divider to remove. */ Menu.prototype.removeMenuDivider = function (menuItemID) { var menuItem, $HTMLMenuItem; if (!menuItemID) { console.error("removeMenuDivider(): missing required parameters: menuItemID"); return; } menuItem = getMenuItem(menuItemID); if (!menuItem) { console.error("removeMenuDivider(): parameter menuItemID: %s is not a valid menu item id", menuItemID); return; } if (!menuItem.isDivider) { console.error("removeMenuDivider(): parameter menuItemID: %s is not a menu divider", menuItemID); return; } if (_isHTMLMenu(this.id)) { // Targeting parent to get the menu divider
    and the
  • that contains it $HTMLMenuItem = $(_getHTMLMenuItem(menuItemID)).parent(); if ($HTMLMenuItem) { $HTMLMenuItem.remove(); } else { console.error("removeMenuDivider(): HTML menu divider not found: %s", menuItemID); return; } } else { brackets.app.removeMenuItem(menuItem.dividerId, function (err) { if (err) { console.error("removeMenuDivider() -- divider not found: %s (error: %s)", menuItemID, err); } }); } if (!menuItemMap[menuItemID]) { console.error("removeMenuDivider(): menu divider not found in menuItemMap: %s", menuItemID); return; } delete menuItemMap[menuItemID]; }; /** * Adds a new menu item with the specified id and display text. The insertion position is * specified via the relativeID and position arguments which describe a position * relative to another MenuItem or MenuGroup. It is preferred that plug-ins * insert new MenuItems relative to a menu section rather than a specific * MenuItem (see Menu Section Constants). * * TODO: Sub-menus are not yet supported, but when they are implemented this API will * allow adding new MenuItems to sub-menus as well. * * Note, keyBindings are bound to Command objects not MenuItems. The provided keyBindings * will be bound to the supplied Command object rather than the MenuItem. * * @param {!string | Command} command - the command the menu will execute. * Pass Menus.DIVIDER for a menu divider, or just call addMenuDivider() instead. * @param {?string | Array.<{key: string, platform: string}>} keyBindings - register one * one or more key bindings to associate with the supplied command. * @param {?string} position - constant defining the position of new MenuItem relative to * other MenuItems. Values: * - With no relativeID, use Menus.FIRST or LAST (default is LAST) * - Relative to a command id, use BEFORE or AFTER (required) * - Relative to a MenuSection, use FIRST_IN_SECTION or LAST_IN_SECTION (required) * @param {?string} relativeID - command id OR one of the MenuSection.* constants. Required * for all position constants except FIRST and LAST. * * @return {MenuItem} the newly created MenuItem */ Menu.prototype.addMenuItem = function (command, keyBindings, position, relativeID) { var menuID = this.id, id, $menuItem, menuItem, name, commandID; if (!command) { console.error("addMenuItem(): missing required parameters: command"); return null; } if (typeof (command) === "string") { if (command === DIVIDER) { name = DIVIDER; commandID = _getNextMenuItemDividerID(); } else { commandID = command; command = CommandManager.get(commandID); if (!command) { console.error("addMenuItem(): commandID not found: " + commandID); return null; } name = command.getName(); } } else { commandID = command.getID(); name = command.getName(); } // Internal id is the a composite of the parent menu id and the command id. id = this._getMenuItemId(commandID); if (menuItemMap[id]) { console.log("MenuItem added with same id of existing MenuItem: " + id); return null; } // create MenuItem menuItem = new MenuItem(id, command); menuItemMap[id] = menuItem; // create MenuItem DOM if (_isHTMLMenu(this.id)) { if (name === DIVIDER) { $menuItem = $("

  • "); } else { // Create the HTML Menu $menuItem = $("
  • "); $menuItem.on("click", function () { menuItem._command.execute(); }); var self = this; $menuItem.on("mouseenter", function () { self.closeSubMenu(); }); } // Insert menu item var $relativeElement = this._getRelativeMenuItem(relativeID, position); _insertInList($("li#" + StringUtils.jQueryIdEscape(this.id) + " > ul.dropdown-menu"), $menuItem, position, $relativeElement); } else { var bindings = KeyBindingManager.getKeyBindings(commandID), binding, bindingStr = "", displayStr = ""; if (bindings && bindings.length > 0) { binding = bindings[bindings.length - 1]; bindingStr = binding.displayKey || binding.key; } if (bindingStr.length > 0) { displayStr = KeyBindingManager.formatKeyDescriptor(bindingStr); } if (position === FIRST_IN_SECTION || position === LAST_IN_SECTION) { if (!relativeID.hasOwnProperty("sectionMarker")) { console.error("Bad Parameter in _getRelativeMenuItem(): relativeID must be a MenuSection when position refers to a menu section"); return null; } // For sections, pass in the marker for that section. relativeID = relativeID.sectionMarker; } brackets.app.addMenuItem(this.id, name, commandID, bindingStr, displayStr, position, relativeID, function (err) { switch (err) { case NO_ERROR: break; case ERR_INVALID_PARAMS: console.error("addMenuItem(): Invalid Parameters when adding the command " + commandID); break; case ERR_NOT_FOUND: console.error("_getRelativeMenuItem(): MenuItem with Command id " + relativeID + " not found in the Menu " + menuID); break; default: console.error("addMenuItem(); Unknown Error (" + err + ") when adding the command " + commandID); } }); menuItem.isNative = true; } // Initialize MenuItem state if (menuItem.isDivider) { menuItem.dividerId = commandID; } else { if (keyBindings) { // Add key bindings. The MenuItem listens to the Command object to update MenuItem DOM with shortcuts. if (!Array.isArray(keyBindings)) { keyBindings = [keyBindings]; } } // Note that keyBindings passed during MenuItem creation take precedent over any existing key bindings KeyBindingManager.addBinding(commandID, keyBindings); // Look for existing key bindings _addExistingKeyBinding(menuItem); menuItem._checkedChanged(); menuItem._enabledChanged(); menuItem._nameChanged(); } return menuItem; }; /** * Inserts divider item in menu. * @param {?string} position - constant defining the position of new the divider relative * to other MenuItems. Default is LAST. (see Insertion position constants). * @param {?string} relativeID - id of menuItem, sub-menu, or menu section that the new * divider will be positioned relative to. Required for all position constants * except FIRST and LAST * * @return {MenuItem} the newly created divider */ Menu.prototype.addMenuDivider = function (position, relativeID) { return this.addMenuItem(DIVIDER, "", position, relativeID); }; /** * NOT IMPLEMENTED * Alternative JSON based API to addMenuItem() * * All properties are required unless noted as optional. * * @param { Array.<{ * id: string, * command: string | Command, * ?bindings: string | Array.<{key: string, platform: string}>, * }>} jsonStr * } * @param {?string} position - constant defining the position of new the MenuItem relative * to other MenuItems. Default is LAST. (see Insertion position constants). * @param {?string} relativeID - id of menuItem, sub-menu, or menu section that the new * menuItem will be positioned relative to. Required when position is * AFTER or BEFORE, ignored when position is FIRST or LAST. * * @return {MenuItem} the newly created MenuItem */ // Menu.prototype.createMenuItemsFromJSON = function (jsonStr, position, relativeID) { // NOT IMPLEMENTED // }; /** * NOT IMPLEMENTED * @param {!string} text displayed in menu item * @param {!string} id * @param {?string} position - constant defining the position of new the MenuItem relative * to other MenuItems. Default is LAST. (see Insertion position constants) * @param {?string} relativeID - id of menuItem, sub-menu, or menu section that the new * menuItem will be positioned relative to. Required when position is * AFTER or BEFORE, ignored when position is FIRST or LAST. * * @return {MenuItem} newly created menuItem for sub-menu */ // MenuItem.prototype.createSubMenu = function (text, id, position, relativeID) { // NOT IMPLEMENTED // }; /** * * Creates a new submenu and a menuItem and adds the menuItem of the submenu * to the menu and returns the submenu. * * A submenu will have the same structure of a menu with a additional field * parentMenuItem which has the reference of the submenu's parent menuItem. * A submenu will raise the following events: * - beforeSubMenuOpen * - beforeSubMenuClose * * Note, This function will create only a context submenu. * * TODO: Make this function work for Menus * * * @param {!string} name displayed in menu item of the submenu * @param {!string} id * @param {?string} position - constant defining the position of new MenuItem of the submenu relative to * other MenuItems. Values: * - With no relativeID, use Menus.FIRST or LAST (default is LAST) * - Relative to a command id, use BEFORE or AFTER (required) * - Relative to a MenuSection, use FIRST_IN_SECTION or LAST_IN_SECTION (required) * @param {?string} relativeID - command id OR one of the MenuSection.* constants. Required * for all position constants except FIRST and LAST. * * @return {Menu} the newly created submenu */ Menu.prototype.addSubMenu = function (name, id, position, relativeID) { if (!name || !id) { console.error("addSubMenu(): missing required parameters: name and id"); return null; } // Guard against duplicate context menu ids if (contextMenuMap[id]) { console.log("Context menu added with id of existing Context Menu: " + id); return null; } var menu = new ContextMenu(id); contextMenuMap[id] = menu; var menuItemID = this.id + "-" + id; if (menuItemMap[menuItemID]) { console.log("MenuItem added with same id of existing MenuItem: " + id); return null; } // create MenuItem var menuItem = new MenuItem(menuItemID, SUBMENU); menuItemMap[menuItemID] = menuItem; menu.parentMenuItem = menuItem; // create MenuItem DOM if (_isHTMLMenu(this.id)) { // Create the HTML MenuItem var $menuItem = $("
  • " + "" + name + "" + "" + "
  • "); var self = this; $menuItem.on("mouseenter", function(e) { if (self.openSubMenu && self.openSubMenu.id === menu.id) { return; } self.closeSubMenu(); self.openSubMenu = menu; menu.open(); }); // Insert menu item var $relativeElement = this._getRelativeMenuItem(relativeID, position); _insertInList($("li#" + StringUtils.jQueryIdEscape(this.id) + " > ul.dropdown-menu"), $menuItem, position, $relativeElement); } else { // TODO: add submenus for native menus } return menu; }; /** * Removes the specified submenu from this Menu. * * Note, this function will only remove context submenus * * TODO: Make this function work for Menus * * @param {!string} subMenuID - the menu id of the submenu to remove. */ Menu.prototype.removeSubMenu = function (subMenuID) { var subMenu, parentMenuItem, commandID = ""; if (!subMenuID) { console.error("removeSubMenu(): missing required parameters: subMenuID"); return; } subMenu = getContextMenu(subMenuID); if (!subMenu || !subMenu.parentMenuItem) { console.error("removeSubMenu(): parameter subMenuID: %s is not a valid submenu id", subMenuID); return; } parentMenuItem = subMenu.parentMenuItem; if (!menuItemMap[parentMenuItem.id]) { console.error("removeSubMenu(): parent menuItem not found in menuItemMap: %s", parentMenuItem.id); return; } // Remove all of the menu items in the submenu _.forEach(menuItemMap, function (value, key) { if (_.startsWith(key, subMenuID)) { if (value.isDivider) { subMenu.removeMenuDivider(key); } else { commandID = value.getCommand(); subMenu.removeMenuItem(commandID); } } }); if (_isHTMLMenu(this.id)) { $(_getHTMLMenuItem(parentMenuItem.id)).parent().remove(); // remove the menu item $(_getHTMLMenu(subMenuID)).remove(); // remove the menu } else { // TODO: remove submenus for native menus } delete menuItemMap[parentMenuItem.id]; delete contextMenuMap[subMenuID]; }; /** * Closes the submenu if the menu has a submenu open. */ Menu.prototype.closeSubMenu = function() { if (this.openSubMenu) { this.openSubMenu.close(); this.openSubMenu = null; } }; /** * Gets the Command associated with a MenuItem * @return {Command} */ MenuItem.prototype.getCommand = function () { return this._command; }; /** * NOT IMPLEMENTED * Returns the parent MenuItem if the menu item is a sub-menu, returns null otherwise. * @return {MenuItem} */ // MenuItem.prototype.getParentMenuItem = function () { // NOT IMPLEMENTED; // }; /** * Returns the parent Menu for this MenuItem * @return {Menu} */ MenuItem.prototype.getParentMenu = function () { var parent = $(_getHTMLMenuItem(this.id)).parents(".dropdown").get(0); if (!parent) { return null; } return getMenu(parent.id); }; /** * Synchronizes MenuItem checked state with underlying Command checked state */ MenuItem.prototype._checkedChanged = function () { var checked = !!this._command.getChecked(); if (this.isNative) { var enabled = !!this._command.getEnabled(); brackets.app.setMenuItemState(this._command.getID(), enabled, checked, function (err) { if (err) { console.log("Error setting menu item state: " + err); } }); } else { ViewUtils.toggleClass($(_getHTMLMenuItem(this.id)), "checked", checked); } }; /** * Synchronizes MenuItem enabled state with underlying Command enabled state */ MenuItem.prototype._enabledChanged = function () { if (this.isNative) { var enabled = !!this._command.getEnabled(); var checked = !!this._command.getChecked(); brackets.app.setMenuItemState(this._command.getID(), enabled, checked, function (err) { if (err) { console.log("Error setting menu item state: " + err); } }); } else { ViewUtils.toggleClass($(_getHTMLMenuItem(this.id)), "disabled", !this._command.getEnabled()); } }; /** * Synchronizes MenuItem name with underlying Command name */ MenuItem.prototype._nameChanged = function () { if (this.isNative) { brackets.app.setMenuTitle(this._command.getID(), this._command.getName(), function (err) { if (err) { console.log("Error setting menu title: " + err); } }); } else { $(_getHTMLMenuItem(this.id)).find(".menu-name").text(this._command.getName()); } }; /** * @private * Updates MenuItem DOM with a keyboard shortcut label */ MenuItem.prototype._keyBindingAdded = function (event, keyBinding) { if (this.isNative) { var shortcutKey = keyBinding.displayKey || keyBinding.key; brackets.app.setMenuItemShortcut(this._command.getID(), shortcutKey, KeyBindingManager.formatKeyDescriptor(shortcutKey), function (err) { if (err) { console.error("Error setting menu item shortcut: " + err); } }); } else { _addKeyBindingToMenuItem($(_getHTMLMenuItem(this.id)), keyBinding.key, keyBinding.displayKey); } }; /** * @private * Updates MenuItem DOM to remove keyboard shortcut label */ MenuItem.prototype._keyBindingRemoved = function (event, keyBinding) { if (this.isNative) { brackets.app.setMenuItemShortcut(this._command.getID(), "", "", function (err) { if (err) { console.error("Error setting menu item shortcut: " + err); } }); } else { var $shortcut = $(_getHTMLMenuItem(this.id)).find(".menu-shortcut"); if ($shortcut.length > 0 && $shortcut.data("key") === keyBinding.key) { // check for any other bindings if (_addExistingKeyBinding(this) === null) { $shortcut.empty(); } } } }; /** * Closes all menus that are open */ function closeAll() { $(".dropdown").removeClass("open"); } /** * Adds a top-level menu to the application menu bar which may be native or HTML-based. * * @param {!string} name - display text for menu * @param {!string} id - unique identifier for a menu. * Core Menus in Brackets use a simple title as an id, for example "file-menu". * Extensions should use the following format: "author.myextension.mymenuname". * @param {?string} position - constant defining the position of new the Menu relative * to other Menus. Default is LAST (see Insertion position constants). * * @param {?string} relativeID - id of Menu the new Menu will be positioned relative to. Required * when position is AFTER or BEFORE, ignored when position is FIRST or LAST * * @return {?Menu} the newly created Menu */ function addMenu(name, id, position, relativeID) { name = _.escape(name); var $menubar = $("#titlebar .nav"), menu; if (!name || !id) { console.error("call to addMenu() is missing required parameters"); return null; } // Guard against duplicate menu ids if (menuMap[id]) { console.log("Menu added with same name and id of existing Menu: " + id); return null; } menu = new Menu(id); menuMap[id] = menu; if (!_isHTMLMenu(id)) { brackets.app.addMenu(name, id, position, relativeID, function (err) { switch (err) { case NO_ERROR: // Make sure name is up to date brackets.app.setMenuTitle(id, name, function (err) { if (err) { console.error("setMenuTitle() -- error: " + err); } }); break; case ERR_UNKNOWN: console.error("addMenu(): Unknown Error when adding the menu " + id); break; case ERR_INVALID_PARAMS: console.error("addMenu(): Invalid Parameters when adding the menu " + id); break; case ERR_NOT_FOUND: console.error("addMenu(): Menu with command " + relativeID + " could not be found when adding the menu " + id); break; default: console.error("addMenu(): Unknown Error (" + err + ") when adding the menu " + id); } }); return menu; } var $toggle = $("" + name + ""), $popUp = $(""), $newMenu = $("").append($toggle).append($popUp); // Insert menu var $relativeElement = relativeID && $(_getHTMLMenu(relativeID)); _insertInList($menubar, $newMenu, position, $relativeElement); // Install ESC key handling PopUpManager.addPopUp($popUp, closeAll, false); // todo error handling return menu; } /** * Removes a top-level menu from the application menu bar which may be native or HTML-based. * * @param {!string} id - unique identifier for a menu. * Core Menus in Brackets use a simple title as an id, for example "file-menu". * Extensions should use the following format: "author.myextension.mymenuname". */ function removeMenu(id) { var menu, commandID = ""; if (!id) { console.error("removeMenu(): missing required parameter: id"); return; } if (!menuMap[id]) { console.error("removeMenu(): menu id not found: %s", id); return; } // Remove all of the menu items in the menu menu = getMenu(id); _.forEach(menuItemMap, function (value, key) { if (_.startsWith(key, id)) { if (value.isDivider) { menu.removeMenuDivider(key); } else { commandID = value.getCommand(); menu.removeMenuItem(commandID); } } }); if (_isHTMLMenu(id)) { $(_getHTMLMenu(id)).remove(); } else { brackets.app.removeMenu(id, function (err) { if (err) { console.error("removeMenu() -- id not found: " + id + " (error: " + err + ")"); } }); } delete menuMap[id]; } /** * Represents a context menu that can open at a specific location in the UI. * * Clients should not create this object directly and should instead use registerContextMenu() * to create new ContextMenu objects. * * Context menus in brackets may be HTML-based or native so clients should not reach into * the HTML and should instead manipulate ContextMenus through the API. * * Events: * - beforeContextMenuOpen * - beforeContextMenuClose * * @constructor * @extends {Menu} */ function ContextMenu(id) { Menu.apply(this, arguments); var $newMenu = $(""), $popUp = $(""), $toggle = $("").hide(); // assemble the menu fragments $newMenu.append($toggle).append($popUp); // insert into DOM $("#context-menu-bar > ul").append($newMenu); var self = this; PopUpManager.addPopUp($popUp, function () { self.close(); }, false); // Listen to ContextMenu's beforeContextMenuOpen event to first close other popups PopUpManager.listenToContextMenu(this); } ContextMenu.prototype = Object.create(Menu.prototype); ContextMenu.prototype.constructor = ContextMenu; ContextMenu.prototype.parentClass = Menu.prototype; EventDispatcher.makeEventDispatcher(ContextMenu.prototype); /** * Displays the ContextMenu at the specified location and dispatches the * "beforeContextMenuOpen" event or "beforeSubMenuOpen" event (for submenus). * The menu location may be adjusted to prevent clipping by the browser window. * All other menus and ContextMenus will be closed before a new menu * will be closed before a new menu is shown (if the new menu is not * a submenu). * * In case of submenus, the parentMenu of the submenu will not be closed when the * sub menu is open. * * @param {MouseEvent | {pageX:number, pageY:number}} mouseOrLocation - pass a MouseEvent * to display the menu near the mouse or pass in an object with page x/y coordinates * for a specific location.This paramter is not used for submenus. Submenus are always * displayed at a position relative to the parent menu. */ ContextMenu.prototype.open = function (mouseOrLocation) { if (!this.parentMenuItem && (!mouseOrLocation || !mouseOrLocation.hasOwnProperty("pageX") || !mouseOrLocation.hasOwnProperty("pageY"))) { console.error("ContextMenu open(): missing required parameter"); return; } var $window = $(window), escapedId = StringUtils.jQueryIdEscape(this.id), $menuAnchor = $("#" + escapedId), $menuWindow = $("#" + escapedId + " > ul"), posTop, posLeft; // only show context menu if it has menu items if ($menuWindow.children().length <= 0) { return; } // adjust positioning so menu is not clipped off bottom or right if (this.parentMenuItem) { // If context menu is a submenu this.trigger("beforeSubMenuOpen"); var $parentMenuItem = $(_getHTMLMenuItem(this.parentMenuItem.id)); posTop = $parentMenuItem.offset().top; posLeft = $parentMenuItem.offset().left + $parentMenuItem.outerWidth(); var elementRect = { top: posTop, left: posLeft, height: $menuWindow.height() + 25, width: $menuWindow.width() }, clip = ViewUtils.getElementClipSize($window, elementRect); if (clip.bottom > 0) { posTop = Math.max(0, posTop + $parentMenuItem.height() - $menuWindow.height()); } posTop -= 30; // shift top for hidden parent element posLeft += 3; if (clip.right > 0) { posLeft = Math.max(0, posLeft - $parentMenuItem.outerWidth() - $menuWindow.outerWidth()); } } else { this.trigger("beforeContextMenuOpen"); // close all other dropdowns closeAll(); posTop = mouseOrLocation.pageY; posLeft = mouseOrLocation.pageX; var elementRect = { top: posTop, left: posLeft, height: $menuWindow.height() + 25, width: $menuWindow.width() }, clip = ViewUtils.getElementClipSize($window, elementRect); if (clip.bottom > 0) { posTop = Math.max(0, posTop - clip.bottom); } posTop -= 30; // shift top for hidden parent element posLeft += 5; if (clip.right > 0) { posLeft = Math.max(0, posLeft - clip.right); } } // open the context menu at final location $menuAnchor.addClass("open") .css({"left": posLeft, "top": posTop}); }; /** * Closes the context menu. */ ContextMenu.prototype.close = function () { if (this.parentMenuItem) { this.trigger("beforeSubMenuClose"); } else { this.trigger("beforeContextMenuClose"); } this.closeSubMenu(); $("#" + StringUtils.jQueryIdEscape(this.id)).removeClass("open"); }; /** * Detect if current context menu is already open */ ContextMenu.prototype.isOpen = function () { return $("#" + StringUtils.jQueryIdEscape(this.id)).hasClass("open"); }; /** * Associate a context menu to a DOM element. * This static function take care of registering event handlers for the click event * listener and passing the right "position" object to the Context#open method */ ContextMenu.assignContextMenuToSelector = function (selector, cmenu) { $(selector).on("click", function (e) { var buttonOffset, buttonHeight; e.stopPropagation(); if (cmenu.isOpen()) { cmenu.close(); } else { buttonOffset = $(this).offset(); buttonHeight = $(this).outerHeight(); cmenu.open({ pageX: buttonOffset.left, pageY: buttonOffset.top + buttonHeight }); } }); }; /** * Registers new context menu with Brackets. * Extensions should generally use the predefined context menus built into Brackets. Use this * API to add a new context menu to UI that is specific to an extension. * * After registering a new context menu clients should: * - use addMenuItem() to add items to the context menu * - call open() to show the context menu. * For example: * $("#my_ID").contextmenu(function (e) { * if (e.which === 3) { * my_cmenu.open(e); * } * }); * * To make menu items be contextual to things like selection, listen for the "beforeContextMenuOpen" * to make changes to Command objects before the context menu is shown. MenuItems are views of * Commands, which control a MenuItem's name, enabled state, and checked state. * * @param {string} id - unique identifier for context menu. * Core context menus in Brackets use a simple title as an id. * Extensions should use the following format: "author.myextension.mycontextmenu name" * @return {?ContextMenu} the newly created context menu */ function registerContextMenu(id) { if (!id) { console.error("call to registerContextMenu() is missing required parameters"); return null; } // Guard against duplicate menu ids if (contextMenuMap[id]) { console.log("Context Menu added with same name and id of existing Context Menu: " + id); return null; } var cmenu = new ContextMenu(id); contextMenuMap[id] = cmenu; return cmenu; } // Deprecated menu ids DeprecationWarning.deprecateConstant(ContextMenuIds, "WORKING_SET_MENU", "WORKING_SET_CONTEXT_MENU"); DeprecationWarning.deprecateConstant(ContextMenuIds, "WORKING_SET_SETTINGS_MENU", "WORKING_SET_CONFIG_MENU"); // Define public API exports.AppMenuBar = AppMenuBar; exports.ContextMenuIds = ContextMenuIds; exports.MenuSection = MenuSection; exports.BEFORE = BEFORE; exports.AFTER = AFTER; exports.LAST = LAST; exports.FIRST = FIRST; exports.FIRST_IN_SECTION = FIRST_IN_SECTION; exports.LAST_IN_SECTION = LAST_IN_SECTION; exports.DIVIDER = DIVIDER; exports.getMenu = getMenu; exports.getAllMenus = getAllMenus; exports.getMenuItem = getMenuItem; exports.getContextMenu = getContextMenu; exports.addMenu = addMenu; exports.removeMenu = removeMenu; exports.registerContextMenu = registerContextMenu; exports.closeAll = closeAll; exports.Menu = Menu; exports.MenuItem = MenuItem; exports.ContextMenu = ContextMenu; }); ================================================ FILE: src/dependencies.js ================================================ /* * Copyright (c) 2013 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /** * Check for missing dependencies */ window.setTimeout(function () { "use strict"; var key, missingDeps = ""; var deps = { "jQuery": window.$, "RequireJS": window.require }; for (key in deps) { if (deps.hasOwnProperty(key) && !deps[key]) { missingDeps += "
  • " + key + "
  • "; } } if (missingDeps.length > 0) { var str = "

    Missing libraries

    " + "

    Oops! One or more required libraries could not be found.

    " + "
      " + missingDeps + "
    " + "

    If you're running from a local copy of the Brackets source, please make sure submodules are updated by running:

    " + "
    git submodule update --init
    " + "

    If you're still having problems, please contact us via one of the channels mentioned at the bottom of the README.

    " + "

    Reload Brackets

    "; window.document.write(str); } }, 1000); ================================================ FILE: src/document/ChangedDocumentTracker.js ================================================ /* * Copyright (c) 2012 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /** * Defines a ChangedDocumentTracker class to monitor changes to files in the current project. */ define(function (require, exports, module) { "use strict"; var DocumentManager = require("document/DocumentManager"), ProjectManager = require("project/ProjectManager"); /** * Tracks "change" events on opened Documents. Used to monitor changes * to documents in-memory and update caches. Assumes all documents have * changed when the Brackets window loses and regains focus. Does not * read timestamps of files on disk. Clients may optionally track file * timestamps on disk independently. * @constructor */ function ChangedDocumentTracker() { var self = this; this._changedPaths = {}; this._windowFocus = true; this._addListener = this._addListener.bind(this); this._removeListener = this._removeListener.bind(this); this._onChange = this._onChange.bind(this); this._onWindowFocus = this._onWindowFocus.bind(this); DocumentManager.on("afterDocumentCreate", function (event, doc) { // Only track documents in the current project if (ProjectManager.isWithinProject(doc.file.fullPath)) { self._addListener(doc); } }); DocumentManager.on("beforeDocumentDelete", function (event, doc) { // In case a document somehow remains loaded after its project // has been closed, unconditionally attempt to remove the listener. self._removeListener(doc); }); $(window).focus(this._onWindowFocus); } /** * @private * Assumes all files are changed when the window loses and regains focus. */ ChangedDocumentTracker.prototype._addListener = function (doc) { doc.on("change", this._onChange); }; /** * @private */ ChangedDocumentTracker.prototype._removeListener = function (doc) { doc.off("change", this._onChange); }; /** * @private * Assumes all files are changed when the window loses and regains focus. */ ChangedDocumentTracker.prototype._onWindowFocus = function (event, doc) { this._windowFocus = true; }; /** * @private * Tracks changed documents. */ ChangedDocumentTracker.prototype._onChange = function (event, doc) { // if it was already changed, and the client hasn't reset the tracker, // then leave it changed. this._changedPaths[doc.file.fullPath] = true; }; /** * Empty the set of dirty paths. Begin tracking new dirty documents. */ ChangedDocumentTracker.prototype.reset = function () { this._changedPaths = {}; this._windowFocus = false; }; /** * Check if a file path is dirty. * @param {!string} file path * @return {!boolean} Returns true if the file was dirtied since the last reset. */ ChangedDocumentTracker.prototype.isPathChanged = function (path) { return this._windowFocus || this._changedPaths[path]; }; /** * Get the set of changed paths since the last reset. * @return {Array.} Changed file paths */ ChangedDocumentTracker.prototype.getChangedPaths = function () { return $.makeArray(this._changedPaths); }; module.exports = ChangedDocumentTracker; }); ================================================ FILE: src/document/Document.js ================================================ /* * Copyright (c) 2012 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ define(function (require, exports, module) { "use strict"; var EditorManager = require("editor/EditorManager"), EventDispatcher = require("utils/EventDispatcher"), FileUtils = require("file/FileUtils"), InMemoryFile = require("document/InMemoryFile"), PerfUtils = require("utils/PerfUtils"), LanguageManager = require("language/LanguageManager"), CodeMirror = require("thirdparty/CodeMirror/lib/codemirror"), _ = require("thirdparty/lodash"); /** * Model for the contents of a single file and its current modification state. * See DocumentManager documentation for important usage notes. * * Document dispatches these events: * * __change__ -- When the text of the editor changes (including due to undo/redo). * * Passes ({Document}, {ChangeList}), where ChangeList is an array * of change record objects. Each change record looks like: * * { from: start of change, expressed as {line: , ch: }, * to: end of change, expressed as {line: , ch: }, * text: array of lines of text to replace existing text } * * The line and ch offsets are both 0-based. * * The ch offset in "from" is inclusive, but the ch offset in "to" is exclusive. For example, * an insertion of new content (without replacing existing content) is expressed by a range * where from and to are the same. * * If "from" and "to" are undefined, then this is a replacement of the entire text content. * * IMPORTANT: If you listen for the "change" event, you MUST also addRef() the document * (and releaseRef() it whenever you stop listening). You should also listen to the "deleted" * event. * * __deleted__ -- When the file for this document has been deleted. All views onto the document should * be closed. The document will no longer be editable or dispatch "change" events. * * __languageChanged__ -- When the value of getLanguage() has changed. 2nd argument is the old value, * 3rd argument is the new value. * * @constructor * @param {!File} file Need not lie within the project. * @param {!Date} initialTimestamp File's timestamp when we read it off disk. * @param {!string} rawText Text content of the file. */ function Document(file, initialTimestamp, rawText) { this.file = file; this.editable = !file.readOnly; this._updateLanguage(); this.refreshText(rawText, initialTimestamp, true); // List of full editors which are initialized as master editors for this doc. this._associatedFullEditors = []; } EventDispatcher.makeEventDispatcher(Document.prototype); /** * Number of clients who want this Document to stay alive. The Document is listed in * DocumentManager._openDocuments whenever refCount > 0. */ Document.prototype._refCount = 0; /** * The File for this document. Need not lie within the project. * If Document is untitled, this is an InMemoryFile object. * @type {!File} */ Document.prototype.file = null; /** * The Language for this document. Will be resolved by file extension in the constructor * @type {!Language} */ Document.prototype.language = null; /** * Whether this document has unsaved changes or not. * When this changes on any Document, DocumentManager dispatches a "dirtyFlagChange" event. * @type {boolean} */ Document.prototype.isDirty = false; /** * Whether this document is currently being saved. * @type {boolean} */ Document.prototype.isSaving = false; /** * What we expect the file's timestamp to be on disk. If the timestamp differs from this, then * it means the file was modified by an app other than Brackets. * @type {!Date} */ Document.prototype.diskTimestamp = null; /** * The timestamp of the document at the point where the user last said to keep changes that conflict * with the current disk version. Can also be -1, indicating that the file was deleted on disk at the * last point when the user said to keep changes, or null, indicating that the user has not said to * keep changes. * Note that this is a time as returned by Date.getTime(), not a Date object. * @type {?Number} */ Document.prototype.keepChangesTime = null; /** * True while refreshText() is in progress and change notifications shouldn't trip the dirty flag. * @type {boolean} */ Document.prototype._refreshInProgress = false; /** * The text contents of the file, or null if our backing model is _masterEditor. * @type {?string} */ Document.prototype._text = null; /** * Editor object representing the full-size editor UI for this document. May be null if Document * has not yet been modified or been the currentDocument; in that case, our backing model is the * string _text. * @type {?Editor} */ Document.prototype._masterEditor = null; /** * The content's line-endings style. If a Document is created on empty text, or text with * inconsistent line endings, defaults to the current platform's standard endings. * @type {FileUtils.LINE_ENDINGS_CRLF|FileUtils.LINE_ENDINGS_LF} */ Document.prototype._lineEndings = null; /** Add a ref to keep this Document alive */ Document.prototype.addRef = function () { //console.log("+++REF+++ "+this); if (this._refCount === 0) { //console.log("+++ adding to open list"); if (exports.trigger("_afterDocumentCreate", this)) { return; } } this._refCount++; }; /** Remove a ref that was keeping this Document alive */ Document.prototype.releaseRef = function () { //console.log("---REF--- "+this); this._refCount--; if (this._refCount < 0) { console.error("Document ref count has fallen below zero!"); return; } if (this._refCount === 0) { //console.log("--- removing from open list"); if (exports.trigger("_beforeDocumentDelete", this)) { return; } } }; /** * Attach a backing Editor to the Document, enabling setText() to be called. Assumes Editor has * already been initialized with the value of getText(). ONLY Editor should call this (and only * when EditorManager has told it to act as the master editor). * @param {!Editor} masterEditor */ Document.prototype._makeEditable = function (masterEditor) { this._text = null; this._masterEditor = masterEditor; masterEditor.on("change", this._handleEditorChange.bind(this)); }; /** * Detach the backing Editor from the Document, disallowing setText(). The text content is * stored back onto _text so other Document clients continue to have read-only access. ONLY * Editor.destroy() should call this. */ Document.prototype._makeNonEditable = function () { if (!this._masterEditor) { console.error("Document is already non-editable"); } else { // _text represents the raw text, so fetch without normalized line endings this._text = this.getText(true); this._associatedFullEditors.splice(this._associatedFullEditors.indexOf(this._masterEditor), 1); // Identify the most recently created full editor before this and set that as new master editor if (this._associatedFullEditors.length > 0) { this._masterEditor = this._associatedFullEditors[this._associatedFullEditors.length - 1]; } else { this._masterEditor = null; } } }; /** * Toggles the master editor which has gained focus from a pool of full editors * To be used internally by Editor only */ Document.prototype._toggleMasterEditor = function (masterEditor) { // Do a check before processing the request to ensure inline editors are not being set as master editor if (this.file === masterEditor.document.file && this._associatedFullEditors.indexOf(masterEditor) >= 0) { this._masterEditor = masterEditor; } }; /** * Checks and returns if a full editor exists for the provided pane attached to this document * @param {String} paneId * @return {Editor} Attached editor bound to the provided pane id */ Document.prototype._checkAssociatedEditorForPane = function (paneId) { var editorCount, editorForPane; for (editorCount = 0; editorCount < this._associatedFullEditors.length; ++editorCount) { if (this._associatedFullEditors[editorCount]._paneId === paneId) { editorForPane = this._associatedFullEditors[editorCount]; break; } } return editorForPane; }; /** * Disassociates an editor from this document if present in the associated editor list * To be used internally by Editor only when destroyed and not the current master editor for the document */ Document.prototype._disassociateEditor = function (editor) { // Do a check before processing the request to ensure inline editors are not being handled if (this._associatedFullEditors.indexOf(editor) >= 0) { this._associatedFullEditors.splice(this._associatedFullEditors.indexOf(editor), 1); } }; /** * Aassociates a full editor to this document * To be used internally by Editor only when pane marking happens */ Document.prototype._associateEditor = function (editor) { // Do a check before processing the request to ensure inline editors are not being handled if (this._associatedFullEditors.indexOf(editor) === -1) { this._associatedFullEditors.push(editor); } }; /** * Guarantees that _masterEditor is non-null. If needed, asks EditorManager to create a new master * editor bound to this Document (which in turn causes Document._makeEditable() to be called). * Should ONLY be called by Editor and Document. */ Document.prototype._ensureMasterEditor = function () { if (!this._masterEditor) { EditorManager._createUnattachedMasterEditor(this); } }; /** * Returns the document's current contents; may not be saved to disk yet. Whenever this * value changes, the Document dispatches a "change" event. * * @param {boolean=} useOriginalLineEndings If true, line endings in the result depend on the * Document's line endings setting (based on OS & the original text loaded from disk). * If false, line endings are always \n (like all the other Document text getter methods). * @return {string} */ Document.prototype.getText = function (useOriginalLineEndings) { if (this._masterEditor) { // CodeMirror.getValue() always returns text with LF line endings; fix up to match line // endings preferred by the document, if necessary var codeMirrorText = this._masterEditor._codeMirror.getValue(); if (useOriginalLineEndings) { if (this._lineEndings === FileUtils.LINE_ENDINGS_CRLF) { return codeMirrorText.replace(/\n/g, "\r\n"); } } return codeMirrorText; } else { // Optimized path that doesn't require creating master editor if (useOriginalLineEndings) { return this._text; } else { return Document.normalizeText(this._text); } } }; /** Normalizes line endings the same way CodeMirror would */ Document.normalizeText = function (text) { return text.replace(/\r\n/g, "\n"); }; /** * Sets the contents of the document. Treated as an edit. Line endings will be rewritten to * match the document's current line-ending style. * @param {!string} text The text to replace the contents of the document with. */ Document.prototype.setText = function (text) { this._ensureMasterEditor(); this._masterEditor._codeMirror.setValue(text); // _handleEditorChange() triggers "change" event }; /** * @private * Triggers the appropriate events when a change occurs: "change" on the Document instance * and "documentChange" on the Document module. * @param {Object} changeList Changelist in CodeMirror format */ Document.prototype._notifyDocumentChange = function (changeList) { this.trigger("change", this, changeList); exports.trigger("documentChange", this, changeList); }; /** * Sets the contents of the document. Treated as reloading the document from disk: the document * will be marked clean with a new timestamp, the undo/redo history is cleared, and we re-check * the text's line-ending style. CAN be called even if there is no backing editor. * @param {!string} text The text to replace the contents of the document with. * @param {!Date} newTimestamp Timestamp of file at the time we read its new contents from disk. * @param {boolean} initial True if this is the initial load of the document. In that case, * we don't send change events. */ Document.prototype.refreshText = function (text, newTimestamp, initial) { var perfTimerName = PerfUtils.markStart("refreshText:\t" + (!this.file || this.file.fullPath)); // If clean, don't transiently mark dirty during refresh // (we'll still send change events though, of course) this._refreshInProgress = true; if (this._masterEditor) { this._masterEditor._resetText(text); // clears undo history too // _handleEditorChange() triggers "change" event for us } else { this._text = text; if (!initial) { // We fake a change record here that looks like CodeMirror's text change records, but // omits "from" and "to", by which we mean the entire text has changed. // TODO: Dumb to split it here just to join it again in the change handler, but this is // the CodeMirror change format. Should we document our change format to allow this to // either be an array of lines or a single string? this._notifyDocumentChange([{text: text.split(/\r?\n/)}]); } } this._updateTimestamp(newTimestamp); // If Doc was dirty before refresh, reset it to clean now (don't always call, to avoid no-op dirtyFlagChange events) Since // _resetText() above already ensures Editor state is clean, it's safe to skip _markClean() as long as our own state is already clean too. if (this.isDirty) { this._markClean(); } this._refreshInProgress = false; // Sniff line-ending style this._lineEndings = FileUtils.sniffLineEndings(text); if (!this._lineEndings) { this._lineEndings = FileUtils.getPlatformLineEndings(); } exports.trigger("_documentRefreshed", this); PerfUtils.addMeasurement(perfTimerName); }; /** * Adds, replaces, or removes text. If a range is given, the text at that range is replaced with the * given new text; if text == "", then the entire range is effectively deleted. If 'end' is omitted, * then the new text is inserted at that point and all existing text is preserved. Line endings will * be rewritten to match the document's current line-ending style. * * IMPORTANT NOTE: Because of #1688, do not use this in cases where you might be * operating on a linked document (like the main document for an inline editor) * during an outer CodeMirror operation (like a key event that's handled by the * editor itself). A common case of this is code hints in inline editors. In * such cases, use `editor._codeMirror.replaceRange()` instead. This should be * fixed when we migrate to use CodeMirror's native document-linking functionality. * * @param {!string} text Text to insert or replace the range with * @param {!{line:number, ch:number}} start Start of range, inclusive (if 'to' specified) or insertion point (if not) * @param {?{line:number, ch:number}} end End of range, exclusive; optional * @param {?string} origin Optional string used to batch consecutive edits for undo. * If origin starts with "+", then consecutive edits with the same origin will be batched for undo if * they are close enough together in time. * If origin starts with "*", then all consecutive edit with the same origin will be batched for * undo. * Edits with origins starting with other characters will not be batched. * (Note that this is a higher level of batching than batchOperation(), which already batches all * edits within it for undo. Origin batching works across operations.) */ Document.prototype.replaceRange = function (text, start, end, origin) { this._ensureMasterEditor(); this._masterEditor._codeMirror.replaceRange(text, start, end, origin); // _handleEditorChange() triggers "change" event }; /** * Returns the characters in the given range. Line endings are normalized to '\n'. * @param {!{line:number, ch:number}} start Start of range, inclusive * @param {!{line:number, ch:number}} end End of range, exclusive * @return {!string} */ Document.prototype.getRange = function (start, end) { this._ensureMasterEditor(); return this._masterEditor._codeMirror.getRange(start, end); }; /** * Returns the text of the given line (excluding any line ending characters) * @param {number} Zero-based line number * @return {!string} */ Document.prototype.getLine = function (lineNum) { this._ensureMasterEditor(); return this._masterEditor._codeMirror.getLine(lineNum); }; /** * Batches a series of related Document changes. Repeated calls to replaceRange() should be wrapped in a * batch for efficiency. Begins the batch, calls doOperation(), ends the batch, and then returns. * @param {function()} doOperation */ Document.prototype.batchOperation = function (doOperation) { this._ensureMasterEditor(); var self = this; self._masterEditor._codeMirror.operation(doOperation); }; /** * Handles changes from the master backing Editor. Changes are triggered either by direct edits * to that Editor's UI, OR by our setText()/refreshText() methods. * @private */ Document.prototype._handleEditorChange = function (event, editor, changeList) { // Handle editor change event only when it is originated from the master editor for this doc if (this._masterEditor !== editor) { return; } // TODO: This needs to be kept in sync with SpecRunnerUtils.createMockActiveDocument(). In the // future, we should fix things so that we either don't need mock documents or that this // is factored so it will just run in both. if (!this._refreshInProgress) { // Sync isDirty from CodeMirror state var wasDirty = this.isDirty; this.isDirty = !editor._codeMirror.isClean(); // Notify if isDirty just changed (this also auto-adds us to working set if needed) if (wasDirty !== this.isDirty) { exports.trigger("_dirtyFlagChange", this); } } // Notify that Document's text has changed this._notifyDocumentChange(changeList); }; /** * @private */ Document.prototype._markClean = function () { this.isDirty = false; if (this._masterEditor) { this._masterEditor._codeMirror.markClean(); } exports.trigger("_dirtyFlagChange", this); }; /** * @private */ Document.prototype._updateTimestamp = function (timestamp) { this.diskTimestamp = timestamp; // Clear the "keep changes" timestamp since it's no longer relevant. this.keepChangesTime = null; }; /** * Called when the document is saved (which currently happens in DocumentCommandHandlers). Marks the * document not dirty and notifies listeners of the save. */ Document.prototype.notifySaved = function () { if (!this._masterEditor) { console.log("### Warning: saving a Document that is not modifiable!"); } this._markClean(); // TODO: (issue #295) fetching timestamp async creates race conditions (albeit unlikely ones) var thisDoc = this; this.file.stat(function (err, stat) { if (!err) { thisDoc._updateTimestamp(stat.mtime); } else { console.log("Error updating timestamp after saving file: " + thisDoc.file.fullPath); } exports.trigger("_documentSaved", thisDoc); }); }; /** * Adjusts a given position taking a given replaceRange-type edit into account. * If the position is within the original edit range (start and end inclusive), * it gets pushed to the end of the content that replaced the range. Otherwise, * if it's after the edit, it gets adjusted so it refers to the same character * it did before the edit. * @param {!{line:number, ch: number}} pos The position to adjust. * @param {!Array.} textLines The text of the change, split into an array of lines. * @param {!{line: number, ch: number}} start The start of the edit. * @param {!{line: number, ch: number}} end The end of the edit. * @return {{line: number, ch: number}} The adjusted position. */ Document.prototype.adjustPosForChange = function (pos, textLines, start, end) { // Same as CodeMirror.adjustForChange(), but that's a private function // and Marijn would rather not expose it publicly. var change = { text: textLines, from: start, to: end }; if (CodeMirror.cmpPos(pos, start) < 0) { return pos; } if (CodeMirror.cmpPos(pos, end) <= 0) { return CodeMirror.changeEnd(change); } var line = pos.line + change.text.length - (change.to.line - change.from.line) - 1, ch = pos.ch; if (pos.line === change.to.line) { ch += CodeMirror.changeEnd(change).ch - change.to.ch; } return {line: line, ch: ch}; }; /** * Like _.each(), but if given a single item not in an array, acts as * if it were an array containing just that item. */ function oneOrEach(itemOrArr, cb) { if (Array.isArray(itemOrArr)) { _.each(itemOrArr, cb); } else { cb(itemOrArr, 0); } } /** * Helper function for edit operations that operate on multiple selections. Takes an "edit list" * that specifies a list of replaceRanges that should occur, but where all the positions are with * respect to the document state before all the edits (i.e., you don't have to figure out how to fix * up the selections after each sub-edit). Edits must be non-overlapping (in original-document terms). * All the edits are done in a single batch. * * If your edits are structured in such a way that each individual edit would cause its associated * selection to be properly updated, then all you need to specify are the edits themselves, and the * selections will automatically be updated as the edits are performed. However, for some * kinds of edits, you need to fix up the selection afterwards. In that case, you can specify one * or more selections to be associated with each edit. Those selections are assumed to be in terms * of the document state after the edit, *as if* that edit were the only one being performed (i.e., * you don't have to worry about adjusting for the effect of other edits). If you supply these selections, * then this function will adjust them as necessary for the effects of other edits, and then return a * flat list of all the selections, suitable for passing to `setSelections()`. * * @param {!Array.<{edit: {text: string, start:{line: number, ch: number}, end:?{line: number, ch: number}} * | Array.<{text: string, start:{line: number, ch: number}, end:?{line: number, ch: number}}>, * selection: ?{start:{line:number, ch:number}, end:{line:number, ch:number}, * primary:boolean, reversed: boolean, isBeforeEdit: boolean}>} * | ?Array.<{start:{line:number, ch:number}, end:{line:number, ch:number}, * primary:boolean, reversed: boolean, isBeforeEdit: boolean}>}>} edits * Specifies the list of edits to perform in a manner similar to CodeMirror's `replaceRange`. This array * will be mutated. * * `edit` is the edit to perform: * `text` will replace the current contents of the range between `start` and `end`. * If `end` is unspecified, the text is inserted at `start`. * `start` and `end` should be positions relative to the document *ignoring* all other edit descriptions * (i.e., as if you were only performing this one edit on the document). * If any of the edits overlap, an error will be thrown. * * If `selection` is specified, it should be a selection associated with this edit. * If `isBeforeEdit` is set on the selection, the selection will be fixed up for this edit. * If not, it won't be fixed up for this edit, meaning it should be expressed in terms of * the document state after this individual edit is performed (ignoring any other edits). * Note that if you were planning on just specifying `isBeforeEdit` for every selection, you can * accomplish the same thing by simply not passing any selections and letting the editor update * the existing selections automatically. * * Note that `edit` and `selection` can each be either an individual edit/selection, or a group of * edits/selections to apply in order. This can be useful if you need to perform multiple edits in a row * and then specify a resulting selection that shouldn't be fixed up for any of those edits (but should be * fixed up for edits related to other selections). It can also be useful if you have several selections * that should ignore the effects of a given edit because you've fixed them up already (this commonly happens * with line-oriented edits where multiple cursors on the same line should be ignored, but still tracked). * Within an edit group, edit positions must be specified relative to previous edits within that group. Also, * the total bounds of edit groups must not overlap (e.g. edits in one group can't surround an edit from another group). * * @param {?string} origin An optional edit origin that's passed through to each replaceRange(). * @return {Array<{start:{line:number, ch:number}, end:{line:number, ch:number}, primary:boolean, reversed: boolean}>} * The list of passed selections adjusted for the performed edits, if any. */ Document.prototype.doMultipleEdits = function (edits, origin) { var self = this; // Sort the edits backwards, so we don't have to adjust the edit positions as we go along // (though we do have to adjust the selection positions). edits.sort(function (editDesc1, editDesc2) { var edit1 = (Array.isArray(editDesc1.edit) ? editDesc1.edit[0] : editDesc1.edit), edit2 = (Array.isArray(editDesc2.edit) ? editDesc2.edit[0] : editDesc2.edit); // Treat all no-op edits as if they should happen before all other edits (the order // doesn't really matter, as long as they sort out of the way of the real edits). if (!edit1) { return -1; } else if (!edit2) { return 1; } else { return CodeMirror.cmpPos(edit2.start, edit1.start); } }); // Pull out the selections, in the same order as the edits. var result = _.cloneDeep(_.pluck(edits, "selection")); // Preflight the edits to specify "end" if unspecified and make sure they don't overlap. // (We don't want to do it during the actual edits, since we don't want to apply some of // the edits before we find out.) _.each(edits, function (editDesc, index) { oneOrEach(editDesc.edit, function (edit) { if (edit) { if (!edit.end) { edit.end = edit.start; } if (index > 0) { var prevEditGroup = edits[index - 1].edit; // The edits are in reverse order, so we want to make sure this edit ends // before any of the previous ones start. oneOrEach(prevEditGroup, function (prevEdit) { if (CodeMirror.cmpPos(edit.end, prevEdit.start) > 0) { throw new Error("Document.doMultipleEdits(): Overlapping edits specified"); } }); } } }); }); // Perform the edits. this.batchOperation(function () { _.each(edits, function (editDesc, index) { // Perform this group of edits. The edit positions are guaranteed to be okay // since all the previous edits we've done have been later in the document. However, // we have to fix up any selections that overlap or come after the edit. oneOrEach(editDesc.edit, function (edit) { if (edit) { self.replaceRange(edit.text, edit.start, edit.end, origin); // Fix up all the selections *except* the one(s) related to this edit list that // are not "before-edit" selections. var textLines = edit.text.split("\n"); _.each(result, function (selections, selIndex) { if (selections) { oneOrEach(selections, function (sel) { if (sel.isBeforeEdit || selIndex !== index) { sel.start = self.adjustPosForChange(sel.start, textLines, edit.start, edit.end); sel.end = self.adjustPosForChange(sel.end, textLines, edit.start, edit.end); } }); } }); } }); }); }); result = _.chain(result) .filter(function (item) { return item !== undefined; }) .flatten() .sort(function (sel1, sel2) { return CodeMirror.cmpPos(sel1.start, sel2.start); }) .value(); _.each(result, function (item) { delete item.isBeforeEdit; }); return result; }; /* (pretty toString(), to aid debugging) */ Document.prototype.toString = function () { var dirtyInfo = (this.isDirty ? " (dirty!)" : " (clean)"); var editorInfo = (this._masterEditor ? " (Editable)" : " (Non-editable)"); var refInfo = " refs:" + this._refCount; return "[Document " + this.file.fullPath + dirtyInfo + editorInfo + refInfo + "]"; }; /** * Returns the language this document is written in. * The language returned is based on the file extension. * @return {Language} An object describing the language used in this document */ Document.prototype.getLanguage = function () { return this.language; }; /** * Updates the language to match the current mapping given by LanguageManager */ Document.prototype._updateLanguage = function () { var oldLanguage = this.language; this.language = LanguageManager.getLanguageForPath(this.file.fullPath); if (oldLanguage && oldLanguage !== this.language) { this.trigger("languageChanged", oldLanguage, this.language); } }; /** Called when Document.file has been modified (due to a rename) */ Document.prototype._notifyFilePathChanged = function () { // File extension may have changed this._updateLanguage(); }; /** * Is this an untitled document? * * @return {boolean} - whether or not the document is untitled */ Document.prototype.isUntitled = function () { return this.file instanceof InMemoryFile; }; /** * Reloads the document from FileSystem * @return {promise} - to check if reload was successful or not */ Document.prototype.reload = function () { var $deferred = $.Deferred(); var self = this; FileUtils.readAsText(this.file) .done(function (text, readTimestamp) { self.refreshText(text, readTimestamp); $deferred.resolve(); }) .fail(function (error) { console.log("Error reloading contents of " + self.file.fullPath, error); $deferred.reject(); }); return $deferred.promise(); }; // We dispatch events from the module level, and the instance level. Instance events are wired up // in the Document constructor. EventDispatcher.makeEventDispatcher(exports); // Define public API exports.Document = Document; }); ================================================ FILE: src/document/DocumentCommandHandlers.js ================================================ /* * Copyright (c) 2012 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /*jslint regexp: true */ define(function (require, exports, module) { "use strict"; // Load dependent modules var AppInit = require("utils/AppInit"), CommandManager = require("command/CommandManager"), Commands = require("command/Commands"), DeprecationWarning = require("utils/DeprecationWarning"), EventDispatcher = require("utils/EventDispatcher"), ProjectManager = require("project/ProjectManager"), DocumentManager = require("document/DocumentManager"), MainViewManager = require("view/MainViewManager"), EditorManager = require("editor/EditorManager"), FileSystem = require("filesystem/FileSystem"), FileSystemError = require("filesystem/FileSystemError"), FileUtils = require("file/FileUtils"), FileViewController = require("project/FileViewController"), InMemoryFile = require("document/InMemoryFile"), StringUtils = require("utils/StringUtils"), Async = require("utils/Async"), HealthLogger = require("utils/HealthLogger"), Dialogs = require("widgets/Dialogs"), DefaultDialogs = require("widgets/DefaultDialogs"), Strings = require("strings"), PopUpManager = require("widgets/PopUpManager"), PreferencesManager = require("preferences/PreferencesManager"), PerfUtils = require("utils/PerfUtils"), KeyEvent = require("utils/KeyEvent"), Inspector = require("LiveDevelopment/Inspector/Inspector"), Menus = require("command/Menus"), UrlParams = require("utils/UrlParams").UrlParams, StatusBar = require("widgets/StatusBar"), WorkspaceManager = require("view/WorkspaceManager"), LanguageManager = require("language/LanguageManager"), _ = require("thirdparty/lodash"); /** * Handlers for commands related to document handling (opening, saving, etc.) */ /** * Container for label shown above editor; must be an inline element * @type {jQueryObject} */ var _$title = null; /** * Container for dirty dot; must be an inline element * @type {jQueryObject} */ var _$dirtydot = null; /** * Container for _$title; need not be an inline element * @type {jQueryObject} */ var _$titleWrapper = null; /** * Label shown above editor for current document: filename and potentially some of its path * @type {string} */ var _currentTitlePath = null; /** * Determine the dash character for each platform. Use emdash on Mac * and a standard dash on all other platforms. * @type {string} */ var _osDash = brackets.platform === "mac" ? "\u2014" : "-"; /** * String template for window title when no file is open. * @type {string} */ var WINDOW_TITLE_STRING_NO_DOC = "{0} " + _osDash + " {1}"; /** * String template for window title when a file is open. * @type {string} */ var WINDOW_TITLE_STRING_DOC = "{0} ({1}) " + _osDash + " {2}"; /** * Container for _$titleWrapper; if changing title changes this element's height, must kick editor to resize * @type {jQueryObject} */ var _$titleContainerToolbar = null; /** * Last known height of _$titleContainerToolbar * @type {number} */ var _lastToolbarHeight = null; /** * index to use for next, new Untitled document * @type {number} */ var _nextUntitledIndexToUse = 1; /** * prevents reentrancy of browserReload() * @type {boolean} */ var _isReloading = false; /** Unique token used to indicate user-driven cancellation of Save As (as opposed to file IO error) */ var USER_CANCELED = { userCanceled: true }; PreferencesManager.definePreference("defaultExtension", "string", "", { excludeFromHints: true }); EventDispatcher.makeEventDispatcher(exports); /** * Event triggered when File Save is cancelled, when prompted to save dirty files */ var APP_QUIT_CANCELLED = "appQuitCancelled"; /** * JSLint workaround for circular dependency * @type {function} */ var handleFileSaveAs; /** * Updates the title bar with new file title or dirty indicator * @private */ function _updateTitle() { var currentDoc = DocumentManager.getCurrentDocument(), windowTitle = brackets.config.app_title, currentlyViewedFile = MainViewManager.getCurrentlyViewedFile(MainViewManager.ACTIVE_PANE), currentlyViewedPath = currentlyViewedFile && currentlyViewedFile.fullPath, readOnlyString = (currentlyViewedFile && currentlyViewedFile.readOnly) ? "[Read Only] - " : ""; if (!brackets.nativeMenus) { if (currentlyViewedPath) { _$title.text(_currentTitlePath); _$title.attr("title", currentlyViewedPath); if (currentDoc) { // dirty dot is always in DOM so layout doesn't change, and visibility is toggled _$dirtydot.css("visibility", (currentDoc.isDirty) ? "visible" : "hidden"); } else { // hide dirty dot if there is no document _$dirtydot.css("visibility", "hidden"); } } else { _$title.text(""); _$title.attr("title", ""); _$dirtydot.css("visibility", "hidden"); } // Set _$titleWrapper to a fixed width just large enough to accommodate _$title. This seems equivalent to what // the browser would do automatically, but the CSS trick we use for layout requires _$titleWrapper to have a // fixed width set on it (see the "#titlebar" CSS rule for details). _$titleWrapper.css("width", ""); var newWidth = _$title.width(); _$titleWrapper.css("width", newWidth); // Changing the width of the title may cause the toolbar layout to change height, which needs to resize the // editor beneath it (toolbar changing height due to window resize is already caught by EditorManager). var newToolbarHeight = _$titleContainerToolbar.height(); if (_lastToolbarHeight !== newToolbarHeight) { _lastToolbarHeight = newToolbarHeight; WorkspaceManager.recomputeLayout(); } } var projectRoot = ProjectManager.getProjectRoot(); if (projectRoot) { var projectName = projectRoot.name; // Construct shell/browser window title, e.g. "• index.html (myProject) — Brackets" if (currentlyViewedPath) { windowTitle = StringUtils.format(WINDOW_TITLE_STRING_DOC, readOnlyString + _currentTitlePath, projectName, brackets.config.app_title); // Display dirty dot when there are unsaved changes if (currentDoc && currentDoc.isDirty) { windowTitle = "• " + windowTitle; } } else { // A document is not open windowTitle = StringUtils.format(WINDOW_TITLE_STRING_NO_DOC, projectName, brackets.config.app_title); } } window.document.title = windowTitle; } /** * Returns a short title for a given document. * * @param {Document} doc - the document to compute the short title for * @return {string} - a short title for doc. */ function _shortTitleForDocument(doc) { var fullPath = doc.file.fullPath; // If the document is untitled then return the filename, ("Untitled-n.ext"); // otherwise show the project-relative path if the file is inside the // current project or the full absolute path if it's not in the project. if (doc.isUntitled()) { return fullPath.substring(fullPath.lastIndexOf("/") + 1); } else { return ProjectManager.makeProjectRelativeIfPossible(fullPath); } } /** * Handles currentFileChange and filenameChanged events and updates the titlebar */ function handleCurrentFileChange() { var newFile = MainViewManager.getCurrentlyViewedFile(MainViewManager.ACTIVE_PANE); if (newFile) { var newDocument = DocumentManager.getOpenDocumentForPath(newFile.fullPath); if (newDocument) { _currentTitlePath = _shortTitleForDocument(newDocument); } else { _currentTitlePath = ProjectManager.makeProjectRelativeIfPossible(newFile.fullPath); } } else { _currentTitlePath = null; } // Update title text & "dirty dot" display _updateTitle(); } /** * Handles dirtyFlagChange event and updates the title bar if necessary */ function handleDirtyChange(event, changedDoc) { var currentDoc = DocumentManager.getCurrentDocument(); if (currentDoc && changedDoc.file.fullPath === currentDoc.file.fullPath) { _updateTitle(); } } /** * Shows an error dialog indicating that the given file could not be opened due to the given error * @param {!FileSystemError} name * @return {!Dialog} */ function showFileOpenError(name, path) { return Dialogs.showModalDialog( DefaultDialogs.DIALOG_ID_ERROR, Strings.ERROR_OPENING_FILE_TITLE, StringUtils.format( Strings.ERROR_OPENING_FILE, StringUtils.breakableUrl(path), FileUtils.getFileErrorString(name) ) ); } /** * @private * Creates a document and displays an editor for the specified file path. * @param {!string} fullPath * @param {boolean=} silent If true, don't show error message * @param {string=} paneId, the id oi the pane in which to open the file. Can be undefined, a valid pane id or ACTIVE_PANE. * @param {{*}=} options, command options * @return {$.Promise} a jQuery promise that will either * - be resolved with a file for the specified file path or * - be rejected with FileSystemError if the file can not be read. * If paneId is undefined, the ACTIVE_PANE constant */ function _doOpen(fullPath, silent, paneId, options) { var result = new $.Deferred(); // workaround for https://github.com/adobe/brackets/issues/6001 // TODO should be removed once bug is closed. // if we are already displaying a file do nothing but resolve immediately. // this fixes timing issues in test cases. if (MainViewManager.getCurrentlyViewedPath(paneId || MainViewManager.ACTIVE_PANE) === fullPath) { result.resolve(MainViewManager.getCurrentlyViewedFile(paneId || MainViewManager.ACTIVE_PANE)); return result.promise(); } function _cleanup(fileError, fullFilePath) { if (fullFilePath) { // For performance, we do lazy checking of file existence, so it may be in workingset MainViewManager._removeView(paneId, FileSystem.getFileForPath(fullFilePath)); MainViewManager.focusActivePane(); } result.reject(fileError); } function _showErrorAndCleanUp(fileError, fullFilePath) { if (silent) { _cleanup(fileError, fullFilePath); } else { showFileOpenError(fileError, fullFilePath).done(function () { _cleanup(fileError, fullFilePath); }); } } if (!fullPath) { throw new Error("_doOpen() called without fullPath"); } else { var perfTimerName = PerfUtils.markStart("Open File:\t" + fullPath); result.always(function () { PerfUtils.addMeasurement(perfTimerName); }); var file = FileSystem.getFileForPath(fullPath); if (options && options.encoding) { file._encoding = options.encoding; } else { var projectRoot = ProjectManager.getProjectRoot(), context = { location : { scope: "user", layer: "project", layerID: projectRoot.fullPath } }; var encoding = PreferencesManager.getViewState("encoding", context); if (encoding && encoding[fullPath]) { file._encoding = encoding[fullPath]; } } MainViewManager._open(paneId, file, options) .done(function () { result.resolve(file); }) .fail(function (fileError) { _showErrorAndCleanUp(fileError, fullPath); result.reject(); }); } return result.promise(); } /** * @private * Used to track the default directory for the file open dialog */ var _defaultOpenDialogFullPath = null; /** * @private * Opens a file and displays its view (editor, image view, etc...) for the specified path. * If no path is specified, a file prompt is provided for input. * @param {?string} fullPath - The path of the file to open; if it's null we'll prompt for it * @param {boolean=} silent - If true, don't show error message * @param {string=} paneId - the pane in which to open the file. Can be undefined, a valid pane id or ACTIVE_PANE * @param {{*}=} options - options to pass to MainViewManager._open * @return {$.Promise} a jQuery promise resolved with a Document object or * rejected with an err */ function _doOpenWithOptionalPath(fullPath, silent, paneId, options) { var result; paneId = paneId || MainViewManager.ACTIVE_PANE; if (!fullPath) { // Create placeholder deferred result = new $.Deferred(); //first time through, default to the current project path if (!_defaultOpenDialogFullPath) { _defaultOpenDialogFullPath = ProjectManager.getProjectRoot().fullPath; } // Prompt the user with a dialog FileSystem.showOpenDialog(true, false, Strings.OPEN_FILE, _defaultOpenDialogFullPath, null, function (err, paths) { if (!err) { if (paths.length > 0) { // Add all files to the workingset without verifying that // they still exist on disk (for faster opening) var filesToOpen = []; paths.forEach(function (path) { filesToOpen.push(FileSystem.getFileForPath(path)); }); MainViewManager.addListToWorkingSet(paneId, filesToOpen); _doOpen(paths[paths.length - 1], silent, paneId, options) .done(function (file) { _defaultOpenDialogFullPath = FileUtils.getDirectoryPath( MainViewManager.getCurrentlyViewedPath(paneId) ); }) // Send the resulting document that was opened .then(result.resolve, result.reject); } else { // Reject if the user canceled the dialog result.reject(); } } }); } else { result = _doOpen(fullPath, silent, paneId, options); } return result.promise(); } /** * @private * Splits a decorated file path into its parts. * @param {?string} path - a string of the form "fullpath[:lineNumber[:columnNumber]]" * @return {{path: string, line: ?number, column: ?number}} */ function _parseDecoratedPath(path) { var result = {path: path, line: null, column: null}; if (path) { // If the path has a trailing :lineNumber and :columnNumber, strip // these off and assign to result.line and result.column. var matchResult = /(.+?):([0-9]+)(:([0-9]+))?$/.exec(path); if (matchResult) { result.path = matchResult[1]; if (matchResult[2]) { result.line = parseInt(matchResult[2], 10); } if (matchResult[4]) { result.column = parseInt(matchResult[4], 10); } } } return result; } /** * @typedef {{fullPath:?string=, silent:boolean=, paneId:string=}} FileCommandData * fullPath: is in the form "path[:lineNumber[:columnNumber]]" * lineNumber and columnNumber are 1-origin: lines and columns are 1-based */ /** * @typedef {{fullPath:?string=, index:number=, silent:boolean=, forceRedraw:boolean=, paneId:string=}} PaneCommandData * fullPath: is in the form "path[:lineNumber[:columnNumber]]" * lineNumber and columnNumber are 1-origin: lines and columns are 1-based */ /** * Opens the given file and makes it the current file. Does NOT add it to the workingset. * @param {FileCommandData=} commandData - record with the following properties: * fullPath: File to open; * silent: optional flag to suppress error messages; * paneId: optional PaneId (defaults to active pane) * @return {$.Promise} a jQuery promise that will be resolved with a file object */ function handleFileOpen(commandData) { var fileInfo = _parseDecoratedPath(commandData ? commandData.fullPath : null), silent = (commandData && commandData.silent) || false, paneId = (commandData && commandData.paneId) || MainViewManager.ACTIVE_PANE, result = new $.Deferred(); _doOpenWithOptionalPath(fileInfo.path, silent, paneId, commandData && commandData.options) .done(function (file) { HealthLogger.fileOpened(file._path, false, file._encoding); if (!commandData || !commandData.options || !commandData.options.noPaneActivate) { MainViewManager.setActivePaneId(paneId); } // If a line and column number were given, position the editor accordingly. if (fileInfo.line !== null) { if (fileInfo.column === null || (fileInfo.column <= 0)) { fileInfo.column = 1; } // setCursorPos expects line/column numbers as 0-origin, so we subtract 1 EditorManager.getCurrentFullEditor().setCursorPos(fileInfo.line - 1, fileInfo.column - 1, true); } result.resolve(file); }) .fail(function (err) { result.reject(err); }); return result; // Testing notes: here are some recommended manual tests for handleFileOpen, on Macintosh. // Do all tests with brackets already running, and also with brackets not already running. // // drag a file onto brackets icon in desktop (this uses undecorated paths) // drag a file onto brackets icon in taskbar (this uses undecorated paths) // open a file from brackets sidebar (this uses undecorated paths) // from command line: ...../Brackets.app/Contents path - where 'path' is undecorated // from command line: ...../Brackets.app path - where 'path' has the form "path:line" // from command line: ...../Brackets.app path - where 'path' has the form "path:line:column" // from command line: open -a ...../Brackets.app path - where 'path' is undecorated // do "View Source" from Adobe Scout version 1.2 or newer (this will use decorated paths of the form "path:line:column") } /** * Opens the given file, makes it the current file, does NOT add it to the workingset * @param {FileCommandData} commandData * fullPath: File to open; * silent: optional flag to suppress error messages; * paneId: optional PaneId (defaults to active pane) * @return {$.Promise} a jQuery promise that will be resolved with @type {Document} */ function handleDocumentOpen(commandData) { var result = new $.Deferred(); handleFileOpen(commandData) .done(function (file) { // if we succeeded with an open file // then we need to resolve that to a document. // getOpenDocumentForPath will return null if there isn't a // supporting document for that file (e.g. an image) var doc = DocumentManager.getOpenDocumentForPath(file.fullPath); result.resolve(doc); }) .fail(function (err) { result.reject(err); }); return result.promise(); } /** * Opens the given file, makes it the current file, AND adds it to the workingset * @param {!PaneCommandData} commandData - record with the following properties: * fullPath: File to open; * index: optional index to position in workingset (defaults to last); * silent: optional flag to suppress error messages; * forceRedraw: flag to force the working set view redraw; * paneId: optional PaneId (defaults to active pane) * @return {$.Promise} a jQuery promise that will be resolved with a @type {File} */ function handleFileAddToWorkingSetAndOpen(commandData) { return handleFileOpen(commandData).done(function (file) { var paneId = (commandData && commandData.paneId) || MainViewManager.ACTIVE_PANE; MainViewManager.addToWorkingSet(paneId, file, commandData.index, commandData.forceRedraw); HealthLogger.fileOpened(file.fullPath, true); }); } /** * @deprecated * Opens the given file, makes it the current document, AND adds it to the workingset * @param {!PaneCommandData} commandData - record with the following properties: * fullPath: File to open; * index: optional index to position in workingset (defaults to last); * silent: optional flag to suppress error messages; * forceRedraw: flag to force the working set view redraw; * paneId: optional PaneId (defaults to active pane) * @return {$.Promise} a jQuery promise that will be resolved with @type {File} */ function handleFileAddToWorkingSet(commandData) { // This is a legacy deprecated command that // will use the new command and resolve with a document // as the legacy command would only support. DeprecationWarning.deprecationWarning("Commands.FILE_ADD_TO_WORKING_SET has been deprecated. Use Commands.CMD_ADD_TO_WORKINGSET_AND_OPEN instead."); var result = new $.Deferred(); handleFileAddToWorkingSetAndOpen(commandData) .done(function (file) { // if we succeeded with an open file // then we need to resolve that to a document. // getOpenDocumentForPath will return null if there isn't a // supporting document for that file (e.g. an image) var doc = DocumentManager.getOpenDocumentForPath(file.fullPath); result.resolve(doc); }) .fail(function (err) { result.reject(err); }); return result.promise(); } /** * @private * Ensures the suggested file name doesn't already exit. * @param {Directory} dir The directory to use * @param {string} baseFileName The base to start with, "-n" will get appended to make unique * @param {boolean} isFolder True if the suggestion is for a folder name * @return {$.Promise} a jQuery promise that will be resolved with a unique name starting with * the given base name */ function _getUntitledFileSuggestion(dir, baseFileName, isFolder) { var suggestedName = baseFileName + "-" + _nextUntitledIndexToUse++, deferred = $.Deferred(); if (_nextUntitledIndexToUse > 9999) { //we've tried this enough deferred.reject(); } else { var path = dir.fullPath + suggestedName, entry = isFolder ? FileSystem.getDirectoryForPath(path) : FileSystem.getFileForPath(path); entry.exists(function (err, exists) { if (err || exists) { _getUntitledFileSuggestion(dir, baseFileName, isFolder) .then(deferred.resolve, deferred.reject); } else { deferred.resolve(suggestedName); } }); } return deferred.promise(); } /** * Prevents re-entrancy into handleFileNewInProject() * * handleFileNewInProject() first prompts the user to name a file and then asynchronously writes the file when the * filename field loses focus. This boolean prevent additional calls to handleFileNewInProject() when an existing * file creation call is outstanding */ var fileNewInProgress = false; /** * Bottleneck function for creating new files and folders in the project tree. * @private * @param {boolean} isFolder - true if creating a new folder, false if creating a new file */ function _handleNewItemInProject(isFolder) { if (fileNewInProgress) { ProjectManager.forceFinishRename(); return; } fileNewInProgress = true; // Determine the directory to put the new file // If a file is currently selected in the tree, put it next to it. // If a directory is currently selected in the tree, put it in it. // If an Untitled document is selected or nothing is selected in the tree, put it at the root of the project. var baseDirEntry, selected = ProjectManager.getFileTreeContext(); if ((!selected) || (selected instanceof InMemoryFile)) { selected = ProjectManager.getProjectRoot(); } if (selected.isFile) { baseDirEntry = FileSystem.getDirectoryForPath(selected.parentPath); } baseDirEntry = baseDirEntry || selected; // Create the new node. The createNewItem function does all the heavy work // of validating file name, creating the new file and selecting. function createWithSuggestedName(suggestedName) { return ProjectManager.createNewItem(baseDirEntry, suggestedName, false, isFolder) .always(function () { fileNewInProgress = false; }); } return _getUntitledFileSuggestion(baseDirEntry, Strings.UNTITLED, isFolder) .then(createWithSuggestedName, createWithSuggestedName.bind(undefined, Strings.UNTITLED)); } /** * Create a new untitled document in the workingset, and make it the current document. * Promise is resolved (synchronously) with the newly-created Document. */ function handleFileNew() { //var defaultExtension = PreferencesManager.get("defaultExtension"); //if (defaultExtension) { // defaultExtension = "." + defaultExtension; //} var defaultExtension = ""; // disable preference setting for now var doc = DocumentManager.createUntitledDocument(_nextUntitledIndexToUse++, defaultExtension); MainViewManager._edit(MainViewManager.ACTIVE_PANE, doc); HealthLogger.sendAnalyticsData( HealthLogger.commonStrings.USAGE + HealthLogger.commonStrings.FILE_OPEN + HealthLogger.commonStrings.FILE_NEW, HealthLogger.commonStrings.USAGE, HealthLogger.commonStrings.FILE_OPEN, HealthLogger.commonStrings.FILE_NEW ); return new $.Deferred().resolve(doc).promise(); } /** * Create a new file in the project tree. */ function handleFileNewInProject() { _handleNewItemInProject(false); } /** * Create a new folder in the project tree. */ function handleNewFolderInProject() { _handleNewItemInProject(true); } /** * @private * Shows an Error modal dialog * @param {string} name * @param {string} path * @return {Dialog} */ function _showSaveFileError(name, path) { return Dialogs.showModalDialog( DefaultDialogs.DIALOG_ID_ERROR, Strings.ERROR_SAVING_FILE_TITLE, StringUtils.format( Strings.ERROR_SAVING_FILE, StringUtils.breakableUrl(path), FileUtils.getFileErrorString(name) ) ); } /** * Saves a document to its existing path. Does NOT support untitled documents. * @param {!Document} docToSave * @param {boolean=} force Ignore CONTENTS_MODIFIED errors from the FileSystem * @return {$.Promise} a promise that is resolved with the File of docToSave (to mirror * the API of _doSaveAs()). Rejected in case of IO error (after error dialog dismissed). */ function doSave(docToSave, force) { var result = new $.Deferred(), file = docToSave.file; function handleError(error) { _showSaveFileError(error, file.fullPath) .done(function () { result.reject(error); }); } function handleContentsModified() { Dialogs.showModalDialog( DefaultDialogs.DIALOG_ID_ERROR, Strings.EXT_MODIFIED_TITLE, StringUtils.format( Strings.EXT_MODIFIED_WARNING, StringUtils.breakableUrl(docToSave.file.fullPath) ), [ { className : Dialogs.DIALOG_BTN_CLASS_LEFT, id : Dialogs.DIALOG_BTN_SAVE_AS, text : Strings.SAVE_AS }, { className : Dialogs.DIALOG_BTN_CLASS_NORMAL, id : Dialogs.DIALOG_BTN_CANCEL, text : Strings.CANCEL }, { className : Dialogs.DIALOG_BTN_CLASS_PRIMARY, id : Dialogs.DIALOG_BTN_OK, text : Strings.SAVE_AND_OVERWRITE } ] ) .done(function (id) { if (id === Dialogs.DIALOG_BTN_CANCEL) { result.reject(); } else if (id === Dialogs.DIALOG_BTN_OK) { // Re-do the save, ignoring any CONTENTS_MODIFIED errors doSave(docToSave, true).then(result.resolve, result.reject); } else if (id === Dialogs.DIALOG_BTN_SAVE_AS) { // Let the user choose a different path at which to write the file handleFileSaveAs({doc: docToSave}).then(result.resolve, result.reject); } }); } function trySave() { // We don't want normalized line endings, so it's important to pass true to getText() FileUtils.writeText(file, docToSave.getText(true), force) .done(function () { docToSave.notifySaved(); result.resolve(file); HealthLogger.fileSaved(docToSave); }) .fail(function (err) { if (err === FileSystemError.CONTENTS_MODIFIED) { handleContentsModified(); } else { handleError(err); } }); } if (docToSave.isDirty) { if (docToSave.keepChangesTime) { // The user has decided to keep conflicting changes in the editor. Check to make sure // the file hasn't changed since they last decided to do that. docToSave.file.stat(function (err, stat) { // If the file has been deleted on disk, the stat will return an error, but that's fine since // that means there's no file to overwrite anyway, so the save will succeed without us having // to set force = true. if (!err && docToSave.keepChangesTime === stat.mtime.getTime()) { // OK, it's safe to overwrite the file even though we never reloaded the latest version, // since the user already said s/he wanted to ignore the disk version. force = true; } trySave(); }); } else { trySave(); } } else { result.resolve(file); } result.always(function () { MainViewManager.focusActivePane(); }); return result.promise(); } /** * Reverts the Document to the current contents of its file on disk. Discards any unsaved changes * in the Document. * @private * @param {Document} doc * @param {boolean=} suppressError If true, then a failure to read the file will be ignored and the * resulting promise will be resolved rather than rejected. * @return {$.Promise} a Promise that's resolved when done, or (if suppressError is false) * rejected with a FileSystemError if the file cannot be read (after showing an error * dialog to the user). */ function _doRevert(doc, suppressError) { var result = new $.Deferred(); FileUtils.readAsText(doc.file) .done(function (text, readTimestamp) { doc.refreshText(text, readTimestamp); result.resolve(); }) .fail(function (error) { if (suppressError) { result.resolve(); } else { showFileOpenError(error, doc.file.fullPath) .done(function () { result.reject(error); }); } }); return result.promise(); } /** * Dispatches the app quit cancelled event */ function dispatchAppQuitCancelledEvent() { exports.trigger(exports.APP_QUIT_CANCELLED); } /** * Opens the native OS save as dialog and saves document. * The original document is reverted in case it was dirty. * Text selection and cursor position from the original document * are preserved in the new document. * When saving to the original document the document is saved as if save was called. * @param {Document} doc * @param {?{cursorPos:!Object, selection:!Object, scrollPos:!Object}} settings - properties of * the original document's editor that need to be carried over to the new document * i.e. scrollPos, cursorPos and text selection * @return {$.Promise} a promise that is resolved with the saved document's File. Rejected in * case of IO error (after error dialog dismissed), or if the Save dialog was canceled. */ function _doSaveAs(doc, settings) { var origPath, saveAsDefaultPath, defaultName, result = new $.Deferred(); function _doSaveAfterSaveDialog(path) { var newFile; // Reconstruct old doc's editor's view state, & finally resolve overall promise function _configureEditorAndResolve() { var editor = EditorManager.getActiveEditor(); if (editor) { if (settings) { editor.setSelections(settings.selections); editor.setScrollPos(settings.scrollPos.x, settings.scrollPos.y); } } result.resolve(newFile); } // Replace old document with new one in open editor & workingset function openNewFile() { var fileOpenPromise; if (FileViewController.getFileSelectionFocus() === FileViewController.PROJECT_MANAGER) { // If selection is in the tree, leave workingset unchanged - even if orig file is in the list fileOpenPromise = FileViewController .openAndSelectDocument(path, FileViewController.PROJECT_MANAGER); } else { // If selection is in workingset, replace orig item in place with the new file var info = MainViewManager.findInAllWorkingSets(doc.file.fullPath).shift(); // Remove old file from workingset; no redraw yet since there's a pause before the new file is opened MainViewManager._removeView(info.paneId, doc.file, true); // Add new file to workingset, and ensure we now redraw (even if index hasn't changed) fileOpenPromise = handleFileAddToWorkingSetAndOpen({fullPath: path, paneId: info.paneId, index: info.index, forceRedraw: true}); } // always configure editor after file is opened fileOpenPromise.always(function () { _configureEditorAndResolve(); }); } // Same name as before - just do a regular Save if (path === origPath) { doSave(doc).then(result.resolve, result.reject); return; } doc.isSaving = true; // mark that we're saving the document // First, write document's current text to new file if (doc.file._encoding && doc.file._encoding !== "UTF-8") { var projectRoot = ProjectManager.getProjectRoot(), context = { location : { scope: "user", layer: "project", layerID: projectRoot.fullPath } }; var encoding = PreferencesManager.getViewState("encoding", context); encoding[path] = doc.file._encoding; PreferencesManager.setViewState("encoding", encoding, context); } newFile = FileSystem.getFileForPath(path); newFile._encoding = doc.file._encoding; // Save as warns you when you're about to overwrite a file, so we // explicitly allow "blind" writes to the filesystem in this case, // ignoring warnings about the contents being modified outside of // the editor. FileUtils.writeText(newFile, doc.getText(true), true) .done(function () { // If there were unsaved changes before Save As, they don't stay with the old // file anymore - so must revert the old doc to match disk content. // Only do this if the doc was dirty: _doRevert on a file that is not dirty and // not in the workingset has the side effect of adding it to the workingset. if (doc.isDirty && !(doc.isUntitled())) { // if the file is dirty it must be in the workingset // _doRevert is side effect free in this case _doRevert(doc).always(openNewFile); } else { openNewFile(); } HealthLogger.fileSaved(doc); }) .fail(function (error) { _showSaveFileError(error, path) .done(function () { result.reject(error); }); }) .always(function () { // mark that we're done saving the document doc.isSaving = false; }); } if (doc) { origPath = doc.file.fullPath; // If the document is an untitled document, we should default to project root. if (doc.isUntitled()) { // (Issue #4489) if we're saving an untitled document, go ahead and switch to this document // in the editor, so that if we're, for example, saving several files (ie. Save All), // then the user can visually tell which document we're currently prompting them to save. var info = MainViewManager.findInAllWorkingSets(origPath).shift(); if (info) { MainViewManager._open(info.paneId, doc.file); } // If the document is untitled, default to project root. saveAsDefaultPath = ProjectManager.getProjectRoot().fullPath; } else { saveAsDefaultPath = FileUtils.getDirectoryPath(origPath); } defaultName = FileUtils.getBaseName(origPath); var file = FileSystem.getFileForPath(origPath); if (file instanceof InMemoryFile) { var language = LanguageManager.getLanguageForPath(origPath); if (language) { var fileExtensions = language.getFileExtensions(); if (fileExtensions && fileExtensions.length > 0) { defaultName += "." + fileExtensions[0]; } } } FileSystem.showSaveDialog(Strings.SAVE_FILE_AS, saveAsDefaultPath, defaultName, function (err, selectedPath) { if (!err) { if (selectedPath) { _doSaveAfterSaveDialog(selectedPath); } else { dispatchAppQuitCancelledEvent(); result.reject(USER_CANCELED); } } else { result.reject(err); } }); } else { result.reject(); } return result.promise(); } /** * Saves the given file. If no file specified, assumes the current document. * @param {?{doc: ?Document}} commandData Document to close, or null * @return {$.Promise} resolved with the saved document's File (which MAY DIFFER from the doc * passed in, if the doc was untitled). Rejected in case of IO error (after error dialog * dismissed), or if doc was untitled and the Save dialog was canceled (will be rejected with * USER_CANCELED object). */ function handleFileSave(commandData) { var activeEditor = EditorManager.getActiveEditor(), activeDoc = activeEditor && activeEditor.document, doc = (commandData && commandData.doc) || activeDoc, settings; if (doc && !doc.isSaving) { if (doc.isUntitled()) { if (doc === activeDoc) { settings = { selections: activeEditor.getSelections(), scrollPos: activeEditor.getScrollPos() }; } return _doSaveAs(doc, settings); } else { return doSave(doc); } } return $.Deferred().reject().promise(); } /** * Saves all unsaved documents corresponding to 'fileList'. Returns a Promise that will be resolved * once ALL the save operations have been completed. If ANY save operation fails, an error dialog is * immediately shown but after dismissing we continue saving the other files; after all files have * been processed, the Promise is rejected if any ONE save operation failed (the error given is the * first one encountered). If the user cancels any Save As dialog (for untitled files), the * Promise is immediately rejected. * * @param {!Array.} fileList * @return {!$.Promise} Resolved with {!Array.}, which may differ from 'fileList' * if any of the files were Unsaved documents. Or rejected with {?FileSystemError}. */ function _saveFileList(fileList) { // Do in serial because doSave shows error UI for each file, and we don't want to stack // multiple dialogs on top of each other var userCanceled = false, filesAfterSave = []; return Async.doSequentially( fileList, function (file) { // Abort remaining saves if user canceled any Save As dialog if (userCanceled) { return (new $.Deferred()).reject().promise(); } var doc = DocumentManager.getOpenDocumentForPath(file.fullPath); if (doc) { var savePromise = handleFileSave({doc: doc}); savePromise .done(function (newFile) { filesAfterSave.push(newFile); }) .fail(function (error) { if (error === USER_CANCELED) { userCanceled = true; } }); return savePromise; } else { // workingset entry that was never actually opened - ignore filesAfterSave.push(file); return (new $.Deferred()).resolve().promise(); } }, false // if any save fails, continue trying to save other files anyway; then reject at end ).then(function () { return filesAfterSave; }); } /** * Saves all unsaved documents. See _saveFileList() for details on the semantics. * @return {$.Promise} */ function saveAll() { return _saveFileList(MainViewManager.getWorkingSet(MainViewManager.ALL_PANES)); } /** * Prompts user with save as dialog and saves document. * @return {$.Promise} a promise that is resolved once the save has been completed */ handleFileSaveAs = function (commandData) { // Default to current document if doc is null var doc = null, settings; if (commandData) { doc = commandData.doc; } else { var activeEditor = EditorManager.getActiveEditor(); if (activeEditor) { doc = activeEditor.document; settings = {}; settings.selections = activeEditor.getSelections(); settings.scrollPos = activeEditor.getScrollPos(); } } // doc may still be null, e.g. if no editors are open, but _doSaveAs() does a null check on // doc. return _doSaveAs(doc, settings); }; /** * Saves all unsaved documents. * @return {$.Promise} a promise that is resolved once ALL the saves have been completed; or rejected * after all operations completed if any ONE of them failed. */ function handleFileSaveAll() { return saveAll(); } /** * Closes the specified file: removes it from the workingset, and closes the main editor if one * is open. Prompts user about saving changes first, if document is dirty. * * @param {?{file: File, promptOnly:boolean}} commandData Optional bag of arguments: * file - File to close; assumes the current document if not specified. * promptOnly - If true, only displays the relevant confirmation UI and does NOT actually * close the document. This is useful when chaining file-close together with other user * prompts that may be cancelable. * _forceClose - If true, closes the document without prompting even if there are unsaved * changes. Only for use in unit tests. * @return {$.Promise} a promise that is resolved when the file is closed, or if no file is open. * FUTURE: should we reject the promise if no file is open? */ function handleFileClose(commandData) { var file, promptOnly, _forceClose, _spawnedRequest, paneId = MainViewManager.ACTIVE_PANE; if (commandData) { file = commandData.file; promptOnly = commandData.promptOnly; _forceClose = commandData._forceClose; paneId = commandData.paneId || paneId; _spawnedRequest = commandData.spawnedRequest || false; } // utility function for handleFileClose: closes document & removes from workingset function doClose(file) { if (!promptOnly) { MainViewManager._close(paneId, file); HealthLogger.fileClosed(file); } } var result = new $.Deferred(), promise = result.promise(); // Default to current document if doc is null if (!file) { file = MainViewManager.getCurrentlyViewedFile(MainViewManager.ACTIVE_PANE); } // No-op if called when nothing is open; TODO: (issue #273) should command be grayed out instead? if (!file) { result.resolve(); return promise; } var doc = DocumentManager.getOpenDocumentForPath(file.fullPath); if (doc && doc.isDirty && !_forceClose && (MainViewManager.isExclusiveToPane(doc.file, paneId) || _spawnedRequest)) { // Document is dirty: prompt to save changes before closing if only the document is exclusively // listed in the requested pane or this is part of a list close request var filename = FileUtils.getBaseName(doc.file.fullPath); Dialogs.showModalDialog( DefaultDialogs.DIALOG_ID_SAVE_CLOSE, Strings.SAVE_CLOSE_TITLE, StringUtils.format( Strings.SAVE_CLOSE_MESSAGE, StringUtils.breakableUrl(filename) ), [ { className : Dialogs.DIALOG_BTN_CLASS_LEFT, id : Dialogs.DIALOG_BTN_DONTSAVE, text : Strings.DONT_SAVE }, { className : Dialogs.DIALOG_BTN_CLASS_NORMAL, id : Dialogs.DIALOG_BTN_CANCEL, text : Strings.CANCEL }, { className : Dialogs.DIALOG_BTN_CLASS_PRIMARY, id : Dialogs.DIALOG_BTN_OK, text : Strings.SAVE } ] ) .done(function (id) { if (id === Dialogs.DIALOG_BTN_CANCEL) { dispatchAppQuitCancelledEvent(); result.reject(); } else if (id === Dialogs.DIALOG_BTN_OK) { // "Save" case: wait until we confirm save has succeeded before closing handleFileSave({doc: doc}) .done(function (newFile) { doClose(newFile); result.resolve(); }) .fail(function () { result.reject(); }); } else { // "Don't Save" case: even though we're closing the main editor, other views of // the Document may remain in the UI. So we need to revert the Document to a clean // copy of whatever's on disk. doClose(file); // Only reload from disk if we've executed the Close for real. if (promptOnly) { result.resolve(); } else { // Even if there are no listeners attached to the document at this point, we want // to do the revert anyway, because clients who are listening to the global documentChange // event from the Document module (rather than attaching to the document directly), // such as the Find in Files panel, should get a change event. However, in that case, // we want to ignore errors during the revert, since we don't want a failed revert // to throw a dialog if the document isn't actually open in the UI. var suppressError = !DocumentManager.getOpenDocumentForPath(file.fullPath); _doRevert(doc, suppressError) .then(result.resolve, result.reject); } } }); result.always(function () { MainViewManager.focusActivePane(); }); } else { // File is not open, or IS open but Document not dirty: close immediately doClose(file); MainViewManager.focusActivePane(); result.resolve(); } return promise; } /** * @param {!Array.} list - the list of files to close * @param {boolean} promptOnly - true to just prompt for saving documents with actually closing them. * @param {boolean} _forceClose Whether to force all the documents to close even if they have unsaved changes. For unit testing only. * @return {jQuery.Promise} promise that is resolved or rejected when the function finishes. */ function _closeList(list, promptOnly, _forceClose) { var result = new $.Deferred(), unsavedDocs = []; list.forEach(function (file) { var doc = DocumentManager.getOpenDocumentForPath(file.fullPath); if (doc && doc.isDirty) { unsavedDocs.push(doc); } }); if (unsavedDocs.length === 0 || _forceClose) { // No unsaved changes or we want to ignore them, so we can proceed without a prompt result.resolve(); } else if (unsavedDocs.length === 1) { // Only one unsaved file: show the usual single-file-close confirmation UI var fileCloseArgs = { file: unsavedDocs[0].file, promptOnly: promptOnly, spawnedRequest: true }; handleFileClose(fileCloseArgs).done(function () { // still need to close any other, non-unsaved documents result.resolve(); }).fail(function () { result.reject(); }); } else { // Multiple unsaved files: show a single bulk prompt listing all files var message = Strings.SAVE_CLOSE_MULTI_MESSAGE + FileUtils.makeDialogFileList(_.map(unsavedDocs, _shortTitleForDocument)); Dialogs.showModalDialog( DefaultDialogs.DIALOG_ID_SAVE_CLOSE, Strings.SAVE_CLOSE_TITLE, message, [ { className : Dialogs.DIALOG_BTN_CLASS_LEFT, id : Dialogs.DIALOG_BTN_DONTSAVE, text : Strings.DONT_SAVE }, { className : Dialogs.DIALOG_BTN_CLASS_NORMAL, id : Dialogs.DIALOG_BTN_CANCEL, text : Strings.CANCEL }, { className : Dialogs.DIALOG_BTN_CLASS_PRIMARY, id : Dialogs.DIALOG_BTN_OK, text : Strings.SAVE } ] ) .done(function (id) { if (id === Dialogs.DIALOG_BTN_CANCEL) { dispatchAppQuitCancelledEvent(); result.reject(); } else if (id === Dialogs.DIALOG_BTN_OK) { // Save all unsaved files, then if that succeeds, close all _saveFileList(list).done(function (listAfterSave) { // List of files after save may be different, if any were Untitled result.resolve(listAfterSave); }).fail(function () { result.reject(); }); } else { // "Don't Save" case--we can just go ahead and close all files. result.resolve(); } }); } // If all the unsaved-changes confirmations pan out above, then go ahead & close all editors // NOTE: this still happens before any done() handlers added by our caller, because jQ // guarantees that handlers run in the order they are added. result.done(function (listAfterSave) { listAfterSave = listAfterSave || list; if (!promptOnly) { MainViewManager._closeList(MainViewManager.ALL_PANES, listAfterSave); } }); return result.promise(); } /** * Closes all open files; equivalent to calling handleFileClose() for each document, except * that unsaved changes are confirmed once, in bulk. * @param {?{promptOnly: boolean, _forceClose: boolean}} * If promptOnly is true, only displays the relevant confirmation UI and does NOT * actually close any documents. This is useful when chaining close-all together with * other user prompts that may be cancelable. * If _forceClose is true, forces the files to close with no confirmation even if dirty. * Should only be used for unit test cleanup. * @return {$.Promise} a promise that is resolved when all files are closed */ function handleFileCloseAll(commandData) { return _closeList(MainViewManager.getAllOpenFiles(), (commandData && commandData.promptOnly), (commandData && commandData._forceClose)); } /** * Closes a list of open files; equivalent to calling handleFileClose() for each document, except * that unsaved changes are confirmed once, in bulk. * @param {?{promptOnly: boolean, _forceClose: boolean}} * If promptOnly is true, only displays the relevant confirmation UI and does NOT * actually close any documents. This is useful when chaining close-all together with * other user prompts that may be cancelable. * If _forceClose is true, forces the files to close with no confirmation even if dirty. * Should only be used for unit test cleanup. * @return {$.Promise} a promise that is resolved when all files are closed */ function handleFileCloseList(commandData) { return _closeList(commandData.fileList); } /** * @private - tracks our closing state if we get called again */ var _windowGoingAway = false; /** * @private * Common implementation for close/quit/reload which all mostly * the same except for the final step * @param {Object} commandData - (not referenced) * @param {!function()} postCloseHandler - called after close * @param {!function()} failHandler - called when the save fails to cancel closing the window */ function _handleWindowGoingAway(commandData, postCloseHandler, failHandler) { if (_windowGoingAway) { //if we get called back while we're closing, then just return return (new $.Deferred()).reject().promise(); } return CommandManager.execute(Commands.FILE_CLOSE_ALL, { promptOnly: true }) .done(function () { _windowGoingAway = true; // Give everyone a chance to save their state - but don't let any problems block // us from quitting try { ProjectManager.trigger("beforeAppClose"); } catch (ex) { console.error(ex); } postCloseHandler(); }) .fail(function () { _windowGoingAway = false; if (failHandler) { failHandler(); } }); } /** * @private * Implementation for abortQuit callback to reset quit sequence settings */ function handleAbortQuit() { _windowGoingAway = false; } /** * @private * Implementation for native APP_BEFORE_MENUPOPUP callback to trigger beforeMenuPopup event */ function handleBeforeMenuPopup() { PopUpManager.trigger("beforeMenuPopup"); } /** * Confirms any unsaved changes, then closes the window * @param {Object} command data */ function handleFileCloseWindow(commandData) { return _handleWindowGoingAway( commandData, function () { window.close(); }, function () { // if fail, tell the app to abort any pending quit operation. brackets.app.abortQuit(); } ); } /** Show a textfield to rename whatever is currently selected in the sidebar (or current doc if nothing else selected) */ function handleFileRename() { // Prefer selected sidebar item (which could be a folder) var entry = ProjectManager.getContext(); if (!entry) { // Else use current file (not selected in ProjectManager if not visible in tree or workingset) entry = MainViewManager.getCurrentlyViewedFile(); } if (entry) { ProjectManager.renameItemInline(entry); } } /** Closes the window, then quits the app */ function handleFileQuit(commandData) { return _handleWindowGoingAway( commandData, function () { brackets.app.quit(); }, function () { // if fail, don't exit: user canceled (or asked us to save changes first, but we failed to do so) brackets.app.abortQuit(); } ); } /** Are we already listening for a keyup to call detectDocumentNavEnd()? */ var _addedNavKeyHandler = false; /** * When the Ctrl key is released, if we were in the middle of a next/prev document navigation * sequence, now is the time to end it and update the MRU order. If we allowed the order to update * on every next/prev increment, the 1st & 2nd entries would just switch places forever and we'd * never get further down the list. * @param {jQueryEvent} event Key-up event */ function detectDocumentNavEnd(event) { if (event.keyCode === KeyEvent.DOM_VK_CONTROL) { // Ctrl key MainViewManager.endTraversal(); _addedNavKeyHandler = false; $(window.document.body).off("keyup", detectDocumentNavEnd); } } /** * Navigate to the next/previous (MRU or list order) document. Don't update MRU order yet * @param {!number} inc Delta indicating in which direction we're going * @param {?boolean} listOrder Whether to navigate using MRU or list order. Defaults to MRU order */ function goNextPrevDoc(inc, listOrder) { var result; if (listOrder) { result = MainViewManager.traverseToNextViewInListOrder(inc); } else { result = MainViewManager.traverseToNextViewByMRU(inc); } if (result) { var file = result.file, paneId = result.paneId; MainViewManager.beginTraversal(); CommandManager.execute(Commands.FILE_OPEN, {fullPath: file.fullPath, paneId: paneId }); // Listen for ending of Ctrl+Tab sequence if (!_addedNavKeyHandler) { _addedNavKeyHandler = true; $(window.document.body).keyup(detectDocumentNavEnd); } } } /** Next Doc command handler (MRU order) **/ function handleGoNextDoc() { goNextPrevDoc(+1); } /** Previous Doc command handler (MRU order) **/ function handleGoPrevDoc() { goNextPrevDoc(-1); } /** Next Doc command handler (list order) **/ function handleGoNextDocListOrder() { goNextPrevDoc(+1, true); } /** Previous Doc command handler (list order) **/ function handleGoPrevDocListOrder() { goNextPrevDoc(-1, true); } /** Show in File Tree command handler **/ function handleShowInTree() { ProjectManager.showInTree(MainViewManager.getCurrentlyViewedFile(MainViewManager.ACTIVE_PANE)); } /** Delete file command handler **/ function handleFileDelete() { var entry = ProjectManager.getSelectedItem(); Dialogs.showModalDialog( DefaultDialogs.DIALOG_ID_EXT_DELETED, Strings.CONFIRM_DELETE_TITLE, StringUtils.format( entry.isFile ? Strings.CONFIRM_FILE_DELETE : Strings.CONFIRM_FOLDER_DELETE, StringUtils.breakableUrl(entry.name) ), [ { className : Dialogs.DIALOG_BTN_CLASS_NORMAL, id : Dialogs.DIALOG_BTN_CANCEL, text : Strings.CANCEL }, { className : Dialogs.DIALOG_BTN_CLASS_PRIMARY, id : Dialogs.DIALOG_BTN_OK, text : Strings.DELETE } ] ) .done(function (id) { if (id === Dialogs.DIALOG_BTN_OK) { ProjectManager.deleteItem(entry); } }); } /** Show the selected sidebar (tree or workingset) item in Finder/Explorer */ function handleShowInOS() { var entry = ProjectManager.getSelectedItem(); if (entry) { brackets.app.showOSFolder(entry.fullPath, function (err) { if (err) { console.error("Error showing '" + entry.fullPath + "' in OS folder:", err); } }); } } /** * Disables Brackets' cache via the remote debugging protocol. * @return {$.Promise} A jQuery promise that will be resolved when the cache is disabled and be rejected in any other case */ function _disableCache() { var result = new $.Deferred(); if (brackets.inBrowser) { result.resolve(); } else { brackets.app.getRemoteDebuggingPort(function (err, port){ if ((!err) && port && port > 0) { Inspector.getDebuggableWindows("127.0.0.1", port) .fail(result.reject) .done(function (response) { var page = response[0]; if (!page || !page.webSocketDebuggerUrl) { result.reject(); return; } var _socket = new WebSocket(page.webSocketDebuggerUrl); // Disable the cache _socket.onopen = function _onConnect() { _socket.send(JSON.stringify({ id: 1, method: "Network.setCacheDisabled", params: { "cacheDisabled": true } })); }; // The first message will be the confirmation => disconnected to allow remote debugging of Brackets _socket.onmessage = function _onMessage(e) { _socket.close(); result.resolve(); }; // In case of an error _socket.onerror = result.reject; }); } else { result.reject(); } }); } return result.promise(); } /** * Does a full reload of the browser window * @param {string} href The url to reload into the window */ function browserReload(href) { if (_isReloading) { return; } _isReloading = true; return CommandManager.execute(Commands.FILE_CLOSE_ALL, { promptOnly: true }).done(function () { // Give everyone a chance to save their state - but don't let any problems block // us from quitting try { ProjectManager.trigger("beforeAppClose"); } catch (ex) { console.error(ex); } // Disable the cache to make reloads work _disableCache().always(function () { // Remove all menus to assure every part of Brackets is reloaded _.forEach(Menus.getAllMenus(), function (value, key) { Menus.removeMenu(key); }); // If there's a fragment in both URLs, setting location.href won't actually reload var fragment = href.indexOf("#"); if (fragment !== -1) { href = href.substr(0, fragment); } // Defer for a more successful reload - issue #11539 setTimeout(function () { window.location.href = href; }, 1000); }); }).fail(function () { _isReloading = false; }); } /** * Restarts brackets Handler * @param {boolean=} loadWithoutExtensions - true to restart without extensions, * otherwise extensions are loadeed as it is durning a typical boot */ function handleReload(loadWithoutExtensions) { var href = window.location.href, params = new UrlParams(); // Make sure the Reload Without User Extensions parameter is removed params.parse(); if (loadWithoutExtensions) { if (!params.get("reloadWithoutUserExts")) { params.put("reloadWithoutUserExts", true); } } else { if (params.get("reloadWithoutUserExts")) { params.remove("reloadWithoutUserExts"); } } if (href.indexOf("?") !== -1) { href = href.substring(0, href.indexOf("?")); } if (!params.isEmpty()) { href += "?" + params.toString(); } // Give Mac native menus extra time to update shortcut highlighting. // Prevents the menu highlighting from getting messed up after reload. window.setTimeout(function () { browserReload(href); }, 100); } /** Reload Without Extensions commnad handler **/ var handleReloadWithoutExts = _.partial(handleReload, true); /** * Attach a beforeunload handler to notify user about unsaved changes and URL redirection in CEF. * Prevents data loss in scenario reported under #13708 * Make sure we don't attach this handler if the current window is actually a test window **/ var isTestWindow = (new window.URLSearchParams(window.location.search || "")).get("testEnvironment"); if (!isTestWindow) { window.onbeforeunload = function(e) { var openDocs = DocumentManager.getAllOpenDocuments(); // Detect any unsaved changes openDocs = openDocs.filter(function(doc) { return doc && doc.isDirty; }); // Ensure we are not in normal app-quit or reload workflow if (!_isReloading && !_windowGoingAway) { if (openDocs.length > 0) { return Strings.WINDOW_UNLOAD_WARNING_WITH_UNSAVED_CHANGES; } else { return Strings.WINDOW_UNLOAD_WARNING; } } }; } /** Do some initialization when the DOM is ready **/ AppInit.htmlReady(function () { // If in Reload Without User Extensions mode, update UI and log console message var params = new UrlParams(), $icon = $("#toolbar-extension-manager"), $indicator = $("
    " + Strings.STATUSBAR_USER_EXTENSIONS_DISABLED + "
    "); params.parse(); if (params.get("reloadWithoutUserExts") === "true") { CommandManager.get(Commands.FILE_EXTENSION_MANAGER).setEnabled(false); $icon.css({display: "none"}); StatusBar.addIndicator("status-user-exts", $indicator, true); console.log("Brackets reloaded with extensions disabled"); } // Init DOM elements _$titleContainerToolbar = $("#titlebar"); _$titleWrapper = $(".title-wrapper", _$titleContainerToolbar); _$title = $(".title", _$titleWrapper); _$dirtydot = $(".dirty-dot", _$titleWrapper); }); // Exported for unit testing only exports._parseDecoratedPath = _parseDecoratedPath; // Set some command strings var quitString = Strings.CMD_QUIT, showInOS = Strings.CMD_SHOW_IN_OS; if (brackets.platform === "win") { quitString = Strings.CMD_EXIT; showInOS = Strings.CMD_SHOW_IN_EXPLORER; } else if (brackets.platform === "mac") { showInOS = Strings.CMD_SHOW_IN_FINDER; } // Define public API exports.showFileOpenError = showFileOpenError; exports.APP_QUIT_CANCELLED = APP_QUIT_CANCELLED; // Deprecated commands CommandManager.register(Strings.CMD_ADD_TO_WORKING_SET, Commands.FILE_ADD_TO_WORKING_SET, handleFileAddToWorkingSet); CommandManager.register(Strings.CMD_FILE_OPEN, Commands.FILE_OPEN, handleDocumentOpen); // New commands CommandManager.register(Strings.CMD_ADD_TO_WORKING_SET, Commands.CMD_ADD_TO_WORKINGSET_AND_OPEN, handleFileAddToWorkingSetAndOpen); CommandManager.register(Strings.CMD_FILE_OPEN, Commands.CMD_OPEN, handleFileOpen); // File Commands CommandManager.register(Strings.CMD_FILE_NEW_UNTITLED, Commands.FILE_NEW_UNTITLED, handleFileNew); CommandManager.register(Strings.CMD_FILE_NEW, Commands.FILE_NEW, handleFileNewInProject); CommandManager.register(Strings.CMD_FILE_NEW_FOLDER, Commands.FILE_NEW_FOLDER, handleNewFolderInProject); CommandManager.register(Strings.CMD_FILE_SAVE, Commands.FILE_SAVE, handleFileSave); CommandManager.register(Strings.CMD_FILE_SAVE_ALL, Commands.FILE_SAVE_ALL, handleFileSaveAll); CommandManager.register(Strings.CMD_FILE_SAVE_AS, Commands.FILE_SAVE_AS, handleFileSaveAs); CommandManager.register(Strings.CMD_FILE_RENAME, Commands.FILE_RENAME, handleFileRename); CommandManager.register(Strings.CMD_FILE_DELETE, Commands.FILE_DELETE, handleFileDelete); // Close Commands CommandManager.register(Strings.CMD_FILE_CLOSE, Commands.FILE_CLOSE, handleFileClose); CommandManager.register(Strings.CMD_FILE_CLOSE_ALL, Commands.FILE_CLOSE_ALL, handleFileCloseAll); CommandManager.register(Strings.CMD_FILE_CLOSE_LIST, Commands.FILE_CLOSE_LIST, handleFileCloseList); // Traversal CommandManager.register(Strings.CMD_NEXT_DOC, Commands.NAVIGATE_NEXT_DOC, handleGoNextDoc); CommandManager.register(Strings.CMD_PREV_DOC, Commands.NAVIGATE_PREV_DOC, handleGoPrevDoc); CommandManager.register(Strings.CMD_NEXT_DOC_LIST_ORDER, Commands.NAVIGATE_NEXT_DOC_LIST_ORDER, handleGoNextDocListOrder); CommandManager.register(Strings.CMD_PREV_DOC_LIST_ORDER, Commands.NAVIGATE_PREV_DOC_LIST_ORDER, handleGoPrevDocListOrder); // Special Commands CommandManager.register(showInOS, Commands.NAVIGATE_SHOW_IN_OS, handleShowInOS); CommandManager.register(quitString, Commands.FILE_QUIT, handleFileQuit); CommandManager.register(Strings.CMD_SHOW_IN_TREE, Commands.NAVIGATE_SHOW_IN_FILE_TREE, handleShowInTree); // These commands have no UI representation and are only used internally CommandManager.registerInternal(Commands.APP_ABORT_QUIT, handleAbortQuit); CommandManager.registerInternal(Commands.APP_BEFORE_MENUPOPUP, handleBeforeMenuPopup); CommandManager.registerInternal(Commands.FILE_CLOSE_WINDOW, handleFileCloseWindow); CommandManager.registerInternal(Commands.APP_RELOAD, handleReload); CommandManager.registerInternal(Commands.APP_RELOAD_WITHOUT_EXTS, handleReloadWithoutExts); // Listen for changes that require updating the editor titlebar ProjectManager.on("projectOpen", _updateTitle); DocumentManager.on("dirtyFlagChange", handleDirtyChange); DocumentManager.on("fileNameChange", handleCurrentFileChange); MainViewManager.on("currentFileChange", handleCurrentFileChange); // Reset the untitled document counter before changing projects ProjectManager.on("beforeProjectClose", function () { _nextUntitledIndexToUse = 1; }); }); ================================================ FILE: src/document/DocumentManager.js ================================================ /* * Copyright (c) 2012 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /** * DocumentManager maintains a list of currently 'open' Documents. The DocumentManager is responsible * for coordinating document operations and dispatching certain document events. * * Document is the model for a file's contents; it dispatches events whenever those contents change. * To transiently inspect a file's content, simply get a Document and call getText() on it. However, * to be notified of Document changes or to modify a Document, you MUST call addRef() to ensure the * Document instance 'stays alive' and is shared by all other who read/modify that file. ('Open' * Documents are all Documents that are 'kept alive', i.e. have ref count > 0). * * To get a Document, call getDocumentForPath(); never new up a Document yourself. * * Secretly, a Document may use an Editor instance to act as the model for its internal state. (This * is unavoidable because CodeMirror does not separate its model from its UI). Documents are not * modifiable until they have a backing 'master Editor'. Creation of the backing Editor is owned by * EditorManager. A Document only gets a backing Editor if it opened in an editor. * * A non-modifiable Document may still dispatch change notifications, if the Document was changed * externally on disk. * * Aside from the text content, Document tracks a few pieces of metadata - notably, whether there are * any unsaved changes. * * This module dispatches several events: * * - dirtyFlagChange -- When any Document's isDirty flag changes. The 2nd arg to the listener is the * Document whose flag changed. * - documentSaved -- When a Document's changes have been saved. The 2nd arg to the listener is the * Document that has been saved. * - documentRefreshed -- When a Document's contents have been reloaded from disk. The 2nd arg to the * listener is the Document that has been refreshed. * * NOTE: WorkingSet APIs have been deprecated and have moved to MainViewManager as WorkingSet APIs * Some WorkingSet APIs that have been identified as being used by 3rd party extensions will * emit deprecation warnings and call the WorkingSet APIS to maintain backwards compatibility * * - currentDocumentChange -- Deprecated: use EditorManager activeEditorChange (which covers all editors, * not just full-sized editors) or MainViewManager currentFileChange (which covers full-sized views * only, but is also triggered for non-editor views e.g. image files). * * - fileNameChange -- When the name of a file or folder has changed. The 2nd arg is the old name. * The 3rd arg is the new name. Generally, however, file objects have already been changed by the * time this event is dispatched so code that relies on matching the filename to a file object * will need to compare the newname. * * - pathDeleted -- When a file or folder has been deleted. The 2nd arg is the path that was deleted. * * To listen for events, do something like this: (see EventDispatcher for details on this pattern) * DocumentManager.on("eventname", handler); * * Document objects themselves also dispatch some events - see Document docs for details. */ define(function (require, exports, module) { "use strict"; var _ = require("thirdparty/lodash"); var AppInit = require("utils/AppInit"), EventDispatcher = require("utils/EventDispatcher"), DocumentModule = require("document/Document"), DeprecationWarning = require("utils/DeprecationWarning"), MainViewManager = require("view/MainViewManager"), MainViewFactory = require("view/MainViewFactory"), FileSyncManager = require("project/FileSyncManager"), FileSystem = require("filesystem/FileSystem"), PreferencesManager = require("preferences/PreferencesManager"), FileUtils = require("file/FileUtils"), InMemoryFile = require("document/InMemoryFile"), CommandManager = require("command/CommandManager"), Commands = require("command/Commands"), PerfUtils = require("utils/PerfUtils"), LanguageManager = require("language/LanguageManager"), ProjectManager = require("project/ProjectManager"), Strings = require("strings"); /** * @private * Random path prefix for untitled documents */ var _untitledDocumentPath = "/_brackets_" + _.random(10000000, 99999999); /** * All documents with refCount > 0. Maps Document.file.id -> Document. * @private * @type {Object.} */ var _openDocuments = {}; /** * Returns the existing open Document for the given file, or null if the file is not open ('open' * means referenced by the UI somewhere). If you will hang onto the Document, you must addRef() * it; see {@link #getDocumentForPath} for details. * @param {!string} fullPath * @return {?Document} */ function getOpenDocumentForPath(fullPath) { var id; // Need to walk all open documents and check for matching path. We can't // use getFileForPath(fullPath).id since the file it returns won't match // an Untitled document's InMemoryFile. for (id in _openDocuments) { if (_openDocuments.hasOwnProperty(id)) { if (_openDocuments[id].file.fullPath === fullPath) { return _openDocuments[id]; } } } return null; } /** * Returns the Document that is currently open in the editor UI. May be null. * @return {?Document} */ function getCurrentDocument() { var file = MainViewManager.getCurrentlyViewedFile(MainViewManager.ACTIVE_PANE); if (file) { return getOpenDocumentForPath(file.fullPath); } return null; } /** * Returns a list of items in the working set in UI list order. May be 0-length, but never null. * @deprecated Use MainViewManager.getWorkingSet() instead * @return {Array.} */ function getWorkingSet() { DeprecationWarning.deprecationWarning("Use MainViewManager.getWorkingSet() instead of DocumentManager.getWorkingSet()", true); return MainViewManager.getWorkingSet(MainViewManager.ALL_PANES) .filter(function (file) { // Legacy didn't allow for files with custom viewers return !MainViewFactory.findSuitableFactoryForPath(file.fullPath); }); } /** * Returns the index of the file matching fullPath in the working set. * @deprecated Use MainViewManager.findInWorkingSet() instead * @param {!string} fullPath * @return {number} index, -1 if not found */ function findInWorkingSet(fullPath) { DeprecationWarning.deprecationWarning("Use MainViewManager.findInWorkingSet() instead of DocumentManager.findInWorkingSet()", true); return MainViewManager.findInWorkingSet(MainViewManager.ACTIVE_PANE, fullPath); } /** * Returns all Documents that are 'open' in the UI somewhere (for now, this means open in an * inline editor and/or a full-size editor). Only these Documents can be modified, and only * these Documents are synced with external changes on disk. * @return {Array.} */ function getAllOpenDocuments() { var result = []; var id; for (id in _openDocuments) { if (_openDocuments.hasOwnProperty(id)) { result.push(_openDocuments[id]); } } return result; } /** * Adds the given file to the end of the working set list. * @deprecated Use MainViewManager.addToWorkingSet() instead * @param {!File} file * @param {number=} index Position to add to list (defaults to last); -1 is ignored * @param {boolean=} forceRedraw If true, a working set change notification is always sent * (useful if suppressRedraw was used with removeFromWorkingSet() earlier) */ function addToWorkingSet(file, index, forceRedraw) { DeprecationWarning.deprecationWarning("Use MainViewManager.addToWorkingSet() instead of DocumentManager.addToWorkingSet()", true); MainViewManager.addToWorkingSet(MainViewManager.ACTIVE_PANE, file, index, forceRedraw); } /** * @deprecated Use MainViewManager.addListToWorkingSet() instead * Adds the given file list to the end of the working set list. * If a file in the list has its own custom viewer, then it * is not added into the working set. * Does not change which document is currently open in the editor. * More efficient than calling addToWorkingSet() (in a loop) for * a list of files because there's only 1 redraw at the end * @param {!Array.} fileList */ function addListToWorkingSet(fileList) { DeprecationWarning.deprecationWarning("Use MainViewManager.addListToWorkingSet() instead of DocumentManager.addListToWorkingSet()", true); MainViewManager.addListToWorkingSet(MainViewManager.ACTIVE_PANE, fileList); } /** * closes a list of files * @deprecated Use CommandManager.execute(Commands.FILE_CLOSE_LIST) instead * @param {!Array.} list - list of File objectgs to close */ function removeListFromWorkingSet(list) { DeprecationWarning.deprecationWarning("Use CommandManager.execute(Commands.FILE_CLOSE_LIST, {PaneId: MainViewManager.ALL_PANES, fileList: list}) instead of DocumentManager.removeListFromWorkingSet()", true); CommandManager.execute(Commands.FILE_CLOSE_LIST, {PaneId: MainViewManager.ALL_PANES, fileList: list}); } /** * closes all open files * @deprecated CommandManager.execute(Commands.FILE_CLOSE_ALL) instead */ function closeAll() { DeprecationWarning.deprecationWarning("Use CommandManager.execute(Commands.FILE_CLOSE_ALL,{PaneId: MainViewManager.ALL_PANES}) instead of DocumentManager.closeAll()", true); CommandManager.execute(Commands.FILE_CLOSE_ALL, {PaneId: MainViewManager.ALL_PANES}); } /** * closes the specified file file * @deprecated use CommandManager.execute(Commands.FILE_CLOSE, {File: file}) instead * @param {!File} file - the file to close */ function closeFullEditor(file) { DeprecationWarning.deprecationWarning("Use CommandManager.execute(Commands.FILE_CLOSE, {File: file} instead of DocumentManager.closeFullEditor()", true); CommandManager.execute(Commands.FILE_CLOSE, {File: file}); } /** * opens the specified document for editing in the currently active pane * @deprecated use CommandManager.execute(Commands.CMD_OPEN, {fullPath: doc.file.fullPath}) instead * @param {!Document} document The Document to make current. */ function setCurrentDocument(doc) { DeprecationWarning.deprecationWarning("Use CommandManager.execute(Commands.CMD_OPEN) instead of DocumentManager.setCurrentDocument()", true); CommandManager.execute(Commands.CMD_OPEN, {fullPath: doc.file.fullPath}); } /** * freezes the Working Set MRU list * @deprecated use MainViewManager.beginTraversal() instead */ function beginDocumentNavigation() { DeprecationWarning.deprecationWarning("Use MainViewManager.beginTraversal() instead of DocumentManager.beginDocumentNavigation()", true); MainViewManager.beginTraversal(); } /** * ends document navigation and moves the current file to the front of the MRU list in the Working Set * @deprecated use MainViewManager.endTraversal() instead */ function finalizeDocumentNavigation() { DeprecationWarning.deprecationWarning("Use MainViewManager.endTraversal() instead of DocumentManager.finalizeDocumentNavigation()", true); MainViewManager.endTraversal(); } /** * Get the next or previous file in the working set, in MRU order (relative to currentDocument). May * return currentDocument itself if working set is length 1. * @deprecated use MainViewManager.traverseToNextViewByMRU() instead */ function getNextPrevFile(inc) { DeprecationWarning.deprecationWarning("Use MainViewManager.traverseToNextViewByMRU() instead of DocumentManager.getNextPrevFile()", true); var result = MainViewManager.traverseToNextViewByMRU(inc); if (result) { return result.file; } return null; } /** * Cleans up any loose Documents whose only ref is its own master Editor, and that Editor is not * rooted in the UI anywhere. This can happen if the Editor is auto-created via Document APIs that * trigger _ensureMasterEditor() without making it dirty. E.g. a command invoked on the focused * inline editor makes no-op edits or does a read-only operation. */ function _gcDocuments() { getAllOpenDocuments().forEach(function (doc) { // Is the only ref to this document its own master Editor? if (doc._refCount === 1 && doc._masterEditor) { // Destroy the Editor if it's not being kept alive by the UI MainViewManager._destroyEditorIfNotNeeded(doc); } }); } /** * Gets an existing open Document for the given file, or creates a new one if the Document is * not currently open ('open' means referenced by the UI somewhere). Always use this method to * get Documents; do not call the Document constructor directly. This method is safe to call * in parallel. * * If you are going to hang onto the Document for more than just the duration of a command - e.g. * if you are going to display its contents in a piece of UI - then you must addRef() the Document * and listen for changes on it. (Note: opening the Document in an Editor automatically manages * refs and listeners for that Editor UI). * * If all you need is the Document's getText() value, use the faster getDocumentText() instead. * * @param {!string} fullPath * @param {!object} fileObj actual File|RemoteFile or some other protocol adapter handle * @return {$.Promise} A promise object that will be resolved with the Document, or rejected * with a FileSystemError if the file is not yet open and can't be read from disk. */ function getDocumentForPath(fullPath, fileObj) { var doc = getOpenDocumentForPath(fullPath); if (doc) { // use existing document return new $.Deferred().resolve(doc).promise(); } else { var result = new $.Deferred(), promise = result.promise(); // return null in case of untitled documents if (fullPath.indexOf(_untitledDocumentPath) === 0) { result.resolve(null); return promise; } var file = fileObj || FileSystem.getFileForPath(fullPath), pendingPromise = getDocumentForPath._pendingDocumentPromises[file.id]; if (pendingPromise) { // wait for the result of a previous request return pendingPromise; } else { // log this document's Promise as pending getDocumentForPath._pendingDocumentPromises[file.id] = promise; // create a new document var perfTimerName = PerfUtils.markStart("getDocumentForPath:\t" + fullPath); result.done(function () { PerfUtils.addMeasurement(perfTimerName); }).fail(function () { PerfUtils.finalizeMeasurement(perfTimerName); }); FileUtils.readAsText(file) .always(function () { // document is no longer pending delete getDocumentForPath._pendingDocumentPromises[file.id]; }) .done(function (rawText, readTimestamp) { doc = new DocumentModule.Document(file, readTimestamp, rawText); // This is a good point to clean up any old dangling Documents _gcDocuments(); result.resolve(doc); }) .fail(function (fileError) { result.reject(fileError); }); return promise; } } } /** * Document promises that are waiting to be resolved. It is possible for multiple clients * to request the same document simultaneously before the initial request has completed. * In particular, this happens at app startup where the working set is created and the * initial active document is opened in an editor. This is essential to ensure that only * one Document exists for any File. * @private * @type {Object.} */ getDocumentForPath._pendingDocumentPromises = {}; /** * Gets the text of a Document (including any unsaved changes), or would-be Document if the * file is not actually open. More efficient than getDocumentForPath(). Use when you're reading * document(s) but don't need to hang onto a Document object. * * If the file is open this is equivalent to calling getOpenDocumentForPath().getText(). If the * file is NOT open, this is like calling getDocumentForPath()...getText() but more efficient. * Differs from plain FileUtils.readAsText() in two ways: (a) line endings are still normalized * as in Document.getText(); (b) unsaved changes are returned if there are any. * * @param {!File} file The file to get the text for. * @param {boolean=} checkLineEndings Whether to return line ending information. Default false (slightly more efficient). * @return {$.Promise} * A promise that is resolved with three parameters: * contents - string: the document's text * timestamp - Date: the last time the document was changed on disk (might not be the same as the last time it was changed in memory) * lineEndings - string: the original line endings of the file, one of the FileUtils.LINE_ENDINGS_* constants; * will be null if checkLineEndings was false. * or rejected with a filesystem error. */ function getDocumentText(file, checkLineEndings) { var result = new $.Deferred(), doc = getOpenDocumentForPath(file.fullPath); if (doc) { result.resolve(doc.getText(), doc.diskTimestamp, checkLineEndings ? doc._lineEndings : null); } else { file.read(function (err, contents, encoding, stat) { if (err) { result.reject(err); } else { // Normalize line endings the same way Document would, but don't actually // new up a Document (which entails a bunch of object churn). var originalLineEndings = checkLineEndings ? FileUtils.sniffLineEndings(contents) : null; contents = DocumentModule.Document.normalizeText(contents); result.resolve(contents, stat.mtime, originalLineEndings); } }); } return result.promise(); } /** * Creates an untitled document. The associated File has a fullPath that * looks like /some-random-string/Untitled-counter.fileExt. * * @param {number} counter - used in the name of the new Document's File * @param {string} fileExt - file extension of the new Document's File, including "." * @return {Document} - a new untitled Document */ function createUntitledDocument(counter, fileExt) { var filename = Strings.UNTITLED + "-" + counter + fileExt, fullPath = _untitledDocumentPath + "/" + filename, now = new Date(), file = new InMemoryFile(fullPath, FileSystem); FileSystem.addEntryForPathIfRequired(file, fullPath); return new DocumentModule.Document(file, now, ""); } /** * Reacts to a file being deleted: if there is a Document for this file, causes it to dispatch a * "deleted" event; ensures it's not the currentDocument; and removes this file from the working * set. These actions in turn cause all open editors for this file to close. Discards any unsaved * changes - it is expected that the UI has already confirmed with the user before calling. * * To simply close a main editor when the file hasn't been deleted, use closeFullEditor() or FILE_CLOSE. * * FUTURE: Instead of an explicit notify, we should eventually listen for deletion events on some * sort of "project file model," making this just a private event handler. * * NOTE: This function is not for general consumption, is considered private and may be deprecated * without warning in a future release. * * @param {!File} file */ function notifyFileDeleted(file) { // Notify all editors to close as well exports.trigger("pathDeleted", file.fullPath); var doc = getOpenDocumentForPath(file.fullPath); if (doc) { doc.trigger("deleted"); } // At this point, all those other views SHOULD have released the Doc if (doc && doc._refCount > 0) { console.warn("Deleted " + file.fullPath + " Document still has " + doc._refCount + " references. Did someone addRef() without listening for 'deleted'?"); } } /** * Called after a file or folder has been deleted. This function is responsible * for updating underlying model data and notifying all views of the change. * * @param {string} fullPath The path of the file/folder that has been deleted */ function notifyPathDeleted(fullPath) { // FileSyncManager.syncOpenDocuments() does all the work prompting // the user to save any unsaved changes and then calls us back // via notifyFileDeleted FileSyncManager.syncOpenDocuments(Strings.FILE_DELETED_TITLE); var projectRoot = ProjectManager.getProjectRoot(), context = { location : { scope: "user", layer: "project", layerID: projectRoot.fullPath } }; var encoding = PreferencesManager.getViewState("encoding", context); delete encoding[fullPath]; PreferencesManager.setViewState("encoding", encoding, context); if (!getOpenDocumentForPath(fullPath) && !MainViewManager.findInAllWorkingSets(fullPath).length) { // For images not open in the workingset, // FileSyncManager.syncOpenDocuments() will // not tell us to close those views exports.trigger("pathDeleted", fullPath); } } /** * Called after a file or folder name has changed. This function is responsible * for updating underlying model data and notifying all views of the change. * * @param {string} oldName The old name of the file/folder * @param {string} newName The new name of the file/folder */ function notifyPathNameChanged(oldName, newName) { // Notify all open documents _.forEach(_openDocuments, function (doc) { // TODO: Only notify affected documents? For now _notifyFilePathChange // just updates the language if the extension changed, so it's fine // to call for all open docs. doc._notifyFilePathChanged(); }); // Send a "fileNameChange" event. This will trigger the views to update. exports.trigger("fileNameChange", oldName, newName); } /** * @private * Update document */ function _handleLanguageAdded() { _.forEach(_openDocuments, function (doc) { // No need to look at the new language if this document has one already if (doc.getLanguage().isFallbackLanguage()) { doc._updateLanguage(); } }); } /** * @private * Update document */ function _handleLanguageModified(event, language) { _.forEach(_openDocuments, function (doc) { var docLanguage = doc.getLanguage(); // A modified language can affect a document // - if its language was modified // - if the document doesn't have a language yet and its file extension was added to the modified language if (docLanguage === language || docLanguage.isFallbackLanguage()) { doc._updateLanguage(); } }); } // For compatibility DocumentModule .on("_afterDocumentCreate", function (event, doc) { if (_openDocuments[doc.file.id]) { console.error("Document for this path already in _openDocuments!"); return true; } _openDocuments[doc.file.id] = doc; exports.trigger("afterDocumentCreate", doc); }) .on("_beforeDocumentDelete", function (event, doc) { if (!_openDocuments[doc.file.id]) { console.error("Document with references was not in _openDocuments!"); return true; } exports.trigger("beforeDocumentDelete", doc); delete _openDocuments[doc.file.id]; }) .on("_documentRefreshed", function (event, doc) { exports.trigger("documentRefreshed", doc); }) .on("_dirtyFlagChange", function (event, doc) { // Modules listening on the doc instance notified about dirtyflag change // To be used internally by Editor doc.trigger("_dirtyFlagChange", doc); exports.trigger("dirtyFlagChange", doc); if (doc.isDirty) { MainViewManager.addToWorkingSet(MainViewManager.ACTIVE_PANE, doc.file); // We just dirtied a doc and added it to the active working set // this may have come from an internal dirtying so if it was // added to a working set that had no active document then // open the document // // See: https://github.com/adobe/brackets/issues/9569 // // NOTE: Adding a file to the active working set may not actually add // it to the active working set (e.g. the document was already // opened to the inactive working set.) // // Check that it was actually added to the active working set if (!MainViewManager.getCurrentlyViewedFile() && MainViewManager.findInWorkingSet(MainViewManager.ACTIVE_PANE, doc.file.fullPath) !== -1) { CommandManager.execute(Commands.FILE_OPEN, {fullPath: doc.file.fullPath}); } } }) .on("_documentSaved", function (event, doc) { exports.trigger("documentSaved", doc); }); // Set up event dispatch EventDispatcher.makeEventDispatcher(exports); // This deprecated event is dispatched manually from "currentFileChange" below EventDispatcher.markDeprecated(exports, "currentDocumentChange", "MainViewManager.currentFileChange"); // These deprecated events are automatically dispatched by DeprecationWarning, set up in AppInit.extensionsLoaded() EventDispatcher.markDeprecated(exports, "workingSetAdd", "MainViewManager.workingSetAdd"); EventDispatcher.markDeprecated(exports, "workingSetAddList", "MainViewManager.workingSetAddList"); EventDispatcher.markDeprecated(exports, "workingSetRemove", "MainViewManager.workingSetRemove"); EventDispatcher.markDeprecated(exports, "workingSetRemoveList", "MainViewManager.workingSetRemoveList"); EventDispatcher.markDeprecated(exports, "workingSetSort", "MainViewManager.workingSetSort"); /* * After extensionsLoaded, register the proxying listeners that convert newer events to * deprecated events. This ensures that extension listeners still run later than core * listeners. If we registered these proxies earlier, extensions would get the deprecated * event before some core listeners may have run, which could cause bugs (e.g. WorkingSetView * needs to process these events before the Extension Highlighter extension). */ AppInit.extensionsLoaded(function () { // Listens for the given event on MainViewManager, and triggers a copy of the event // on DocumentManager whenever it occurs function _proxyDeprecatedEvent(eventName) { DeprecationWarning.deprecateEvent(exports, MainViewManager, eventName, eventName, "DocumentManager." + eventName, "MainViewManager." + eventName); } _proxyDeprecatedEvent("workingSetAdd"); _proxyDeprecatedEvent("workingSetAddList"); _proxyDeprecatedEvent("workingSetRemove"); _proxyDeprecatedEvent("workingSetRemoveList"); _proxyDeprecatedEvent("workingSetSort"); }); // Handle file saves that may affect preferences exports.on("documentSaved", function (e, doc) { PreferencesManager.fileChanged(doc.file.fullPath); }); MainViewManager.on("currentFileChange", function (e, newFile, newPaneId, oldFile) { var newDoc = null, oldDoc = null; if (newFile) { newDoc = getOpenDocumentForPath(newFile.fullPath); } if (oldFile) { oldDoc = getOpenDocumentForPath(oldFile.fullPath); } if (oldDoc) { oldDoc.off("languageChanged.DocumentManager"); } if (newDoc) { PreferencesManager._setCurrentLanguage(newDoc.getLanguage().getId()); newDoc.on("languageChanged.DocumentManager", function (e, oldLang, newLang) { PreferencesManager._setCurrentLanguage(newLang.getId()); exports.trigger("currentDocumentLanguageChanged", [oldLang, newLang]); }); } else { PreferencesManager._setCurrentLanguage(undefined); } if (newDoc !== oldDoc) { // Dispatch deprecated event with doc args (not File args as the new event uses) exports.trigger("currentDocumentChange", newDoc, oldDoc); } }); // Deprecated APIs exports.getWorkingSet = getWorkingSet; exports.findInWorkingSet = findInWorkingSet; exports.addToWorkingSet = addToWorkingSet; exports.addListToWorkingSet = addListToWorkingSet; exports.removeListFromWorkingSet = removeListFromWorkingSet; exports.getCurrentDocument = getCurrentDocument; exports.beginDocumentNavigation = beginDocumentNavigation; exports.finalizeDocumentNavigation = finalizeDocumentNavigation; exports.getNextPrevFile = getNextPrevFile; exports.setCurrentDocument = setCurrentDocument; exports.closeFullEditor = closeFullEditor; exports.closeAll = closeAll; // Define public API exports.Document = DocumentModule.Document; exports.getDocumentForPath = getDocumentForPath; exports.getOpenDocumentForPath = getOpenDocumentForPath; exports.getDocumentText = getDocumentText; exports.createUntitledDocument = createUntitledDocument; exports.getAllOpenDocuments = getAllOpenDocuments; // For internal use only exports.notifyPathNameChanged = notifyPathNameChanged; exports.notifyPathDeleted = notifyPathDeleted; exports.notifyFileDeleted = notifyFileDeleted; // Performance measurements PerfUtils.createPerfMeasurement("DOCUMENT_MANAGER_GET_DOCUMENT_FOR_PATH", "DocumentManager.getDocumentForPath()"); // Handle Language change events LanguageManager.on("languageAdded", _handleLanguageAdded); LanguageManager.on("languageModified", _handleLanguageModified); }); ================================================ FILE: src/document/InMemoryFile.js ================================================ /* * Copyright (c) 2013 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /** * Represents a file that will never exist on disk - a placeholder backing file for untitled Documents. NO ONE * other than DocumentManager should create instances of InMemoryFile. It is valid to test for one (`instanceof * InMemoryFile`), but it's better to check `doc.isUntitled` where possible. * * Attempts to read/write an InMemoryFile will always fail, and exists() always yields false. InMemoryFile.fullPath * is just a placeholder, and should not be displayed anywhere in the UI; fullPath IS guaranteed to be unique, however. * * An InMemoryFile is not added to the filesystem index, so if you ask the filesystem anything about this * object, it won't know what you're talking about (`filesystem.getFileForPath(someInMemFile.fullPath)` will not * return someInMemFile). */ define(function (require, exports, module) { "use strict"; var File = require("filesystem/File"), FileSystemError = require("filesystem/FileSystemError"); function InMemoryFile(fullPath, fileSystem) { File.call(this, fullPath, fileSystem); } InMemoryFile.prototype = Object.create(File.prototype); InMemoryFile.prototype.constructor = InMemoryFile; InMemoryFile.prototype.parentClass = File.prototype; // Stub out invalid calls inherited from File /** * Reject any attempts to read the file. * * Read a file as text. * * @param {Object=} options Currently unused. * @param {function (number, string, object)} callback */ InMemoryFile.prototype.read = function (options, callback) { if (typeof (options) === "function") { callback = options; } callback(FileSystemError.NOT_FOUND); }; /** * Rejects any attempts to write the file. * * @param {string} data Data to write. * @param {string=} encoding Encoding for data. Defaults to UTF-8. * @param {!function (err, object)} callback Callback that is passed the * error code and the file's new stats if the write is successful. */ InMemoryFile.prototype.write = function (data, encoding, callback) { if (typeof (encoding) === "function") { callback = encoding; } callback(FileSystemError.NOT_FOUND); }; // Stub out invalid calls inherited from FileSystemEntry InMemoryFile.prototype.exists = function (callback) { callback(null, false); }; InMemoryFile.prototype.stat = function (callback) { callback(FileSystemError.NOT_FOUND); }; InMemoryFile.prototype.unlink = function (callback) { callback(FileSystemError.NOT_FOUND); }; InMemoryFile.prototype.rename = function (newName, callback) { callback(FileSystemError.NOT_FOUND); }; InMemoryFile.prototype.moveToTrash = function (callback) { callback(FileSystemError.NOT_FOUND); }; // Export this class module.exports = InMemoryFile; }); ================================================ FILE: src/document/TextRange.js ================================================ /* * Copyright (c) 2012 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /** */ define(function (require, exports, module) { "use strict"; var EventDispatcher = require("utils/EventDispatcher"); /** * Stores a range of lines that is automatically maintained as the Document changes. The range * MAY drop out of sync with the Document in certain edge cases; startLine & endLine will become * null when that happens. * * Important: you must dispose() a TextRange when you're done with it. Because TextRange addRef()s * the Document (in order to listen to it), you will leak Documents otherwise. * * TextRange dispatches these events: * - change -- When the range boundary line numbers change (due to a Document change) * - contentChange -- When the actual content of the range changes. This might or might not * be accompanied by a change in the boundary line numbers. * - lostSync -- When the backing Document changes in such a way that the range can no longer * accurately be maintained. Generally, occurs whenever an edit spans a range boundary. * After this, startLine & endLine will be unusable (set to null). * Also occurs when the document is deleted, though startLine & endLine won't be modified * These events only ever occur in response to Document changes, so if you are already listening * to the Document, you could ignore the TextRange events and just read its updated value in your * own Document change handler. * * @constructor * * @param {!Document} document * @param {number} startLine First line in range (0-based, inclusive) * @param {number} endLine Last line in range (0-based, inclusive) */ function TextRange(document, startLine, endLine) { this.startLine = startLine; this.endLine = endLine; this.document = document; document.addRef(); // store this-bound versions of listeners so we can remove them later this._handleDocumentChange = this._handleDocumentChange.bind(this); this._handleDocumentDeleted = this._handleDocumentDeleted.bind(this); document.on("change", this._handleDocumentChange); document.on("deleted", this._handleDocumentDeleted); } EventDispatcher.makeEventDispatcher(TextRange.prototype); /** Detaches from the Document. The TextRange will no longer update or send change events */ TextRange.prototype.dispose = function (editor, change) { // Disconnect from Document this.document.releaseRef(); this.document.off("change", this._handleDocumentChange); this.document.off("deleted", this._handleDocumentDeleted); }; /** * Containing document * @type {!Document} */ TextRange.prototype.document = null; /** * Starting Line * @type {?number} Null after "lostSync" is dispatched */ TextRange.prototype.startLine = null; /** * Ending Line * @type {?number} Null after "lostSync" is dispatched */ TextRange.prototype.endLine = null; /** * Applies a single Document change object (out of the linked list of multiple such objects) * to this range. * @param {Object} change The CodeMirror change record. * @return {{hasChanged: boolean, hasContentChanged: boolean}} Whether the range boundary * and/or content has changed. */ TextRange.prototype._applySingleChangeToRange = function (change) { // console.log(this + " applying change to (" + // (change.from && (change.from.line+","+change.from.ch)) + " - " + // (change.to && (change.to.line+","+change.to.ch)) + ")"); // Special case: the range is no longer meaningful since the entire text was replaced if (!change.from || !change.to) { this.startLine = null; this.endLine = null; return {hasChanged: true, hasContentChanged: true}; // Special case: certain changes around the edges of the range are problematic, because // if they're undone, we'll be unable to determine how to fix up the range to include the // undone content. (The "undo" will just look like an insertion outside our bounds.) So // in those cases, we destroy the range instead of fixing it up incorrectly. The specific // cases are: // 1. Edit crosses the start boundary of the inline editor (defined as character 0 // of the first line). // 2. Edit crosses the end boundary of the inline editor (defined as the newline at // the end of the last line). // Note: we also used to disallow edits that start at the beginning of the range (character 0 // of the first line) if they crossed a newline. This was a vestige from before case #1 // was added; now that edits crossing the top boundary (actually, undos of such edits) are // out of the picture, edits on the first line of the range unambiguously belong inside it. } else if ((change.from.line < this.startLine && change.to.line >= this.startLine) || (change.from.line <= this.endLine && change.to.line > this.endLine)) { this.startLine = null; this.endLine = null; return {hasChanged: true, hasContentChanged: true}; // Normal case: update the range end points if any content was added before them. Note that // we don't rely on line handles for this since we want to gracefully handle cases where the // start or end line was deleted during a change. } else { var numAdded = change.text.length - (change.to.line - change.from.line + 1); var result = {hasChanged: false, hasContentChanged: false}; // This logic is so simple because we've already excluded all cases where the change // crosses the range boundaries if (numAdded !== 0) { if (change.to.line < this.startLine) { this.startLine += numAdded; result.hasChanged = true; } if (change.to.line <= this.endLine) { this.endLine += numAdded; result.hasChanged = true; } } if (change.from.line >= this.startLine && change.from.line <= this.endLine) { // Since we know the change doesn't cross the range boundary, as long as the // start of the change is within the range, we know the content changed. result.hasContentChanged = true; } // console.log("Now " + this); return result; } }; /** * Updates the range based on the changeList from a Document "change" event. Dispatches a * "change" event if the range was adjusted at all. Dispatches a "lostSync" event instead if the * range can no longer be accurately maintained. */ TextRange.prototype._applyChangesToRange = function (changeList) { var hasChanged = false, hasContentChanged = false; var i; for (i = 0; i < changeList.length; i++) { // Apply this step of the change list var result = this._applySingleChangeToRange(changeList[i]); hasChanged = hasChanged || result.hasChanged; hasContentChanged = hasContentChanged || result.hasContentChanged; // If we lost sync with the range, just bail now if (this.startLine === null || this.endLine === null) { this.trigger("lostSync"); break; } } if (hasChanged) { this.trigger("change"); } if (hasContentChanged) { this.trigger("contentChange"); } }; TextRange.prototype._handleDocumentChange = function (event, doc, changeList) { this._applyChangesToRange(changeList); }; TextRange.prototype._handleDocumentDeleted = function (event) { this.trigger("lostSync"); }; /* (pretty toString(), to aid debugging) */ TextRange.prototype.toString = function () { return "[TextRange " + this.startLine + "-" + this.endLine + " in " + this.document + "]"; }; // Define public API exports.TextRange = TextRange; }); ================================================ FILE: src/editor/CSSInlineEditor.js ================================================ /* * Copyright (c) 2012 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ define(function (require, exports, module) { "use strict"; // Load dependent modules var CSSUtils = require("language/CSSUtils"), DropdownButton = require("widgets/DropdownButton").DropdownButton, CommandManager = require("command/CommandManager"), Commands = require("command/Commands"), DocumentManager = require("document/DocumentManager"), EditorManager = require("editor/EditorManager"), Editor = require("editor/Editor").Editor, LanguageManager = require("language/LanguageManager"), ProjectManager = require("project/ProjectManager"), FileUtils = require("file/FileUtils"), HTMLUtils = require("language/HTMLUtils"), MultiRangeInlineEditor = require("editor/MultiRangeInlineEditor"), Strings = require("strings"), ViewUtils = require("utils/ViewUtils"), HealthLogger = require("utils/HealthLogger"), _ = require("thirdparty/lodash"); var _newRuleCmd, _newRuleHandlers = []; function _getCSSFilesInProject() { return ProjectManager.getAllFiles(ProjectManager.getLanguageFilter(["css", "less", "scss"])); } /** * Given a position in an HTML editor, returns the relevant selector for the attribute/tag * surrounding that position, or "" if none is found. * @param {!Editor} editor * @param {!{line:Number, ch:Number}} pos * @return {selectorName: {string}, reason: {string}} * @private */ function _getSelectorName(editor, pos) { var tagInfo = HTMLUtils.getTagInfo(editor, pos), selectorName = "", reason; if (tagInfo.position.tokenType === HTMLUtils.TAG_NAME || tagInfo.position.tokenType === HTMLUtils.CLOSING_TAG) { // Type selector selectorName = tagInfo.tagName; } else if (tagInfo.position.tokenType === HTMLUtils.ATTR_NAME || tagInfo.position.tokenType === HTMLUtils.ATTR_VALUE) { if (tagInfo.attr.name === "class") { // Class selector. We only look for the class name // that includes the insertion point. For example, if // the attribute is: // class="error-dialog modal hide" // and the insertion point is inside "modal", we want ".modal" var attributeValue = tagInfo.attr.value; if (/\S/.test(attributeValue)) { var startIndex = attributeValue.substr(0, tagInfo.position.offset).lastIndexOf(" "); var endIndex = attributeValue.indexOf(" ", tagInfo.position.offset); selectorName = "." + attributeValue.substring( startIndex === -1 ? 0 : startIndex + 1, endIndex === -1 ? attributeValue.length : endIndex ); // If the insertion point is surrounded by space between two classnames, selectorName is "." if (selectorName === ".") { selectorName = ""; reason = Strings.ERROR_CSSQUICKEDIT_BETWEENCLASSES; } } else { reason = Strings.ERROR_CSSQUICKEDIT_CLASSNOTFOUND; } } else if (tagInfo.attr.name === "id") { // ID selector var trimmedVal = tagInfo.attr.value.trim(); if (trimmedVal) { selectorName = "#" + trimmedVal; } else { reason = Strings.ERROR_CSSQUICKEDIT_IDNOTFOUND; } } else { reason = Strings.ERROR_CSSQUICKEDIT_UNSUPPORTEDATTR; } } return { selectorName: selectorName, reason: reason }; } /** * @private * Add a new rule for the given selector to the given stylesheet, then add the rule to the * given inline editor. * @param {string} selectorName The selector to create a rule for. * @param {MultiRangeInlineEditor} inlineEditor The inline editor to display the new rule in. * @param {string} path The path to the stylesheet file. */ function _addRule(selectorName, inlineEditor, path) { DocumentManager.getDocumentForPath(path).done(function (styleDoc) { var newRuleInfo = CSSUtils.addRuleToDocument(styleDoc, selectorName, Editor.getUseTabChar(path), Editor.getSpaceUnits(path)); inlineEditor.addAndSelectRange(selectorName, styleDoc, newRuleInfo.range.from.line, newRuleInfo.range.to.line); inlineEditor.editor.setCursorPos(newRuleInfo.pos.line, newRuleInfo.pos.ch); }); } /** * @private * Handle the "new rule" menu item by dispatching it to the handler for the focused inline editor. */ function _handleNewRule() { var inlineEditor = MultiRangeInlineEditor.getFocusedMultiRangeInlineEditor(); if (inlineEditor) { var handlerInfo = _.find(_newRuleHandlers, function (entry) { return entry.inlineEditor === inlineEditor; }); if (handlerInfo) { handlerInfo.handler(); } } } /** Item renderer for stylesheet-picker dropdown */ function _stylesheetListRenderer(item) { var html = "" + _.escape(item.name); if (item.subDirStr) { html += " — " + _.escape(item.subDirStr) + ""; } html += ""; return html; } /** * This function is registered with EditManager as an inline editor provider. It creates a CSSInlineEditor * when cursor is on an HTML tag name, class attribute, or id attribute, find associated * CSS rules and show (one/all of them) in an inline editor. * * @param {!Editor} editor * @param {!{line:Number, ch:Number}} pos * @return {?$.Promise} synchronously resolved with an InlineWidget; or error * {string} if pos is in tag but not in tag name, class attr, or id attr; or null if the * selection isn't even close to a context where we could provide anything. */ function htmlToCSSProvider(hostEditor, pos) { // Only provide a CSS editor when cursor is in HTML content if (hostEditor.getLanguageForSelection().getId() !== "html") { return null; } //Send analytics data for QuickEdit open HealthLogger.sendAnalyticsData( "QuickEditOpen", "usage", "quickEdit", "open" ); // Only provide CSS editor if the selection is within a single line var sel = hostEditor.getSelection(); if (sel.start.line !== sel.end.line) { return null; } // Always use the selection start for determining selector name. The pos // parameter is usually the selection end. var selectorResult = _getSelectorName(hostEditor, sel.start); if (selectorResult.selectorName === "") { return selectorResult.reason || null; } var selectorName = selectorResult.selectorName; var result = new $.Deferred(), cssInlineEditor, cssFileInfos = [], newRuleButton; /** * @private * Callback when item from dropdown list is selected */ function _onDropdownSelect(event, fileInfo) { _addRule(selectorName, cssInlineEditor, fileInfo.fullPath); } /** * @private * Checks to see if there are any stylesheets in the project, and returns the appropriate * "no rules"/"no stylesheets" message accordingly. * @return {$.Promise} a promise that is resolved with the message to show. Never rejected. */ function _getNoRulesMsg() { var result = new $.Deferred(); _getCSSFilesInProject().done(function (fileInfos) { result.resolve(fileInfos.length ? Strings.CSS_QUICK_EDIT_NO_MATCHES : Strings.CSS_QUICK_EDIT_NO_STYLESHEETS); }); return result; } /** * @private * Update the enablement of associated menu commands. */ function _updateCommands() { _newRuleCmd.setEnabled(cssInlineEditor.hasFocus() && !newRuleButton.$button.hasClass("disabled")); } /** * @private * Create a new rule on click. */ function _handleNewRuleClick(e) { if (!newRuleButton.$button.hasClass("disabled")) { if (cssFileInfos.length === 1) { // Just go ahead and create the rule. _addRule(selectorName, cssInlineEditor, cssFileInfos[0].fullPath); } else { // Although not attached to button click in 'dropdown mode', this handler can still be // invoked via the command shortcut. Just toggle dropdown open/closed in that case. newRuleButton.toggleDropdown(); } } } /** * @private * Sort files with LESS/SCSS above CSS, and then within each grouping sort by path & filename * (the same order we use for Find in Files) * @param {!File} a, b * @return {number} */ function _fileComparator(a, b) { var aIsCSS = LanguageManager.getLanguageForPath(a.fullPath).getId() === "css", bIsCSS = LanguageManager.getLanguageForPath(b.fullPath).getId() === "css"; if (aIsCSS && !bIsCSS) { return 1; } else if (!aIsCSS && bIsCSS) { return -1; } else { return FileUtils.comparePaths(a.fullPath, b.fullPath); } } /** * @private * Prepare file list for display */ function _prepFileList(files) { // First, sort list (the same ordering we use for the results list) files.sort(_fileComparator); // Find any files that share the same name (with different path) var fileNames = {}; files.forEach(function (file) { if (!fileNames[file.name]) { fileNames[file.name] = []; } fileNames[file.name].push(file); }); // For any duplicate filenames, set subDirStr to a path snippet the helps // the user distinguish each file in the list. _.forEach(fileNames, function (files) { if (files.length > 1) { var displayPaths = ViewUtils.getDirNamesForDuplicateFiles(files); files.forEach(function (file, i) { file.subDirStr = displayPaths[i]; }); } }); return files; } function _onHostEditorScroll() { newRuleButton.closeDropdown(); } CSSUtils.findMatchingRules(selectorName, hostEditor.document) .done(function (rules) { var inlineEditorDeferred = new $.Deferred(); cssInlineEditor = new MultiRangeInlineEditor.MultiRangeInlineEditor(CSSUtils.consolidateRules(rules), _getNoRulesMsg, CSSUtils.getRangeSelectors, _fileComparator); cssInlineEditor.load(hostEditor); cssInlineEditor.$htmlContent .on("focusin", _updateCommands) .on("focusout", _updateCommands); cssInlineEditor.on("add", function () { inlineEditorDeferred.resolve(); }); cssInlineEditor.on("close", function () { newRuleButton.closeDropdown(); hostEditor.off("scroll", _onHostEditorScroll); }); var $header = $(".inline-editor-header", cssInlineEditor.$htmlContent); newRuleButton = new DropdownButton(Strings.BUTTON_NEW_RULE, [], _stylesheetListRenderer); // actual item list populated later, below newRuleButton.$button.addClass("disabled"); // disabled until list is known newRuleButton.$button.addClass("btn-mini stylesheet-button"); $header.append(newRuleButton.$button); _newRuleHandlers.push({inlineEditor: cssInlineEditor, handler: _handleNewRuleClick}); hostEditor.on("scroll", _onHostEditorScroll); result.resolve(cssInlineEditor); // Now that dialog has been built, collect list of stylesheets var stylesheetsPromise = _getCSSFilesInProject(); // After both the stylesheets are loaded and the inline editor has been added to the DOM, // update the UI accordingly. (Those can happen in either order, so we need to wait for both.) // Note that the stylesheetsPromise needs to be passed first in order for the fileInfos to be // properly passed to the handler, since $.when() passes the results in order of the argument // list. $.when(stylesheetsPromise, inlineEditorDeferred.promise()) .done(function (fileInfos) { cssFileInfos = _prepFileList(fileInfos); // "New Rule" button is disabled by default and gets enabled // here if there are any stylesheets in project if (cssFileInfos.length > 0) { newRuleButton.$button.removeClass("disabled"); if (!rules.length) { // Force focus to the button so the user can create a new rule from the keyboard. newRuleButton.$button.focus(); } if (cssFileInfos.length === 1) { // Make it look & feel like a plain button in this case newRuleButton.$button.removeClass("btn-dropdown"); newRuleButton.$button.on("click", _handleNewRuleClick); } else { // Fill out remaining dropdown attributes otherwise newRuleButton.items = cssFileInfos; newRuleButton.on("select", _onDropdownSelect); } } _updateCommands(); }); }) .fail(function (error) { console.warn("Error in findMatchingRules()", error); result.reject(); }); return result.promise(); } EditorManager.registerInlineEditProvider(htmlToCSSProvider); _newRuleCmd = CommandManager.register(Strings.CMD_CSS_QUICK_EDIT_NEW_RULE, Commands.CSS_QUICK_EDIT_NEW_RULE, _handleNewRule); _newRuleCmd.setEnabled(false); }); ================================================ FILE: src/editor/CodeHintList.js ================================================ /* * Copyright (c) 2012 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ define(function (require, exports, module) { "use strict"; // Load dependent modules var KeyBindingManager = require("command/KeyBindingManager"), Menus = require("command/Menus"), KeyEvent = require("utils/KeyEvent"), StringUtils = require("utils/StringUtils"), ValidationUtils = require("utils/ValidationUtils"), ViewUtils = require("utils/ViewUtils"), PopUpManager = require("widgets/PopUpManager"), Mustache = require("thirdparty/mustache/mustache"); var CodeHintListHTML = require("text!htmlContent/code-hint-list.html"); /** * Displays a popup list of hints for a given editor context. * * @constructor * @param {Editor} editor * @param {boolean} insertHintOnTab Whether pressing tab inserts the selected hint * @param {number} maxResults Maximum hints displayed at once. Defaults to 50 */ function CodeHintList(editor, insertHintOnTab, maxResults) { /** * The list of hints to display * * @type {Array.} */ this.hints = []; /** * The selected position in the list; otherwise -1. * * @type {number} */ this.selectedIndex = -1; /** * The maximum number of hints to display. Can be overriden via maxCodeHints pref * * @type {number} */ this.maxResults = ValidationUtils.isIntegerInRange(maxResults, 1, 1000) ? maxResults : 50; /** * Is the list currently open? * * @type {boolean} */ this.opened = false; /** * The editor context * * @type {Editor} */ this.editor = editor; /** * Whether the currently selected hint should be inserted on a tab key event * * @type {boolean} */ this.insertHintOnTab = insertHintOnTab; /** * Pending text insertion * * @type {string} */ this.pendingText = ""; /** * The hint selection callback function * * @type {Function} */ this.handleSelect = null; /** * The hint list closure callback function * * @type {Function} */ this.handleClose = null; /** * The hint list menu object * * @type {jQuery.Object} */ this.$hintMenu = $("") .append($("") .hide()) .append(""); this._keydownHook = this._keydownHook.bind(this); } /** * Select the item in the hint list at the specified index, or remove the * selection if index < 0. * * @private * @param {number} index */ CodeHintList.prototype._setSelectedIndex = function (index) { var items = this.$hintMenu.find("li"); // Range check index = Math.max(-1, Math.min(index, items.length - 1)); // Clear old highlight if (this.selectedIndex !== -1) { $(items[this.selectedIndex]).find("a").removeClass("highlight"); } this.selectedIndex = index; // Highlight the new selected item, if necessary if (this.selectedIndex !== -1) { var $item = $(items[this.selectedIndex]); var $view = this.$hintMenu.find("ul.dropdown-menu"); $item.find("a").addClass("highlight"); ViewUtils.scrollElementIntoView($view, $item, false); if (this.handleHighlight) { this.handleHighlight($item.find("a"), this.$hintMenu.find("#codehint-desc")); } } }; /** * Appends text to end of pending text. * * @param {string} text */ CodeHintList.prototype.addPendingText = function (text) { this.pendingText += text; }; /** * Removes text from beginning of pending text. * * @param {string} text */ CodeHintList.prototype.removePendingText = function (text) { if (this.pendingText.indexOf(text) === 0) { this.pendingText = this.pendingText.slice(text.length); } }; /** * Rebuilds the list items for the hint list. * * @private */ CodeHintList.prototype._buildListView = function (hintObj) { var self = this, match = hintObj.match, selectInitial = hintObj.selectInitial, view = { hints: [] }, _addHint; this.hints = hintObj.hints; this.hints.handleWideResults = hintObj.handleWideResults; this.enableDescription = hintObj.enableDescription; // if there is no match, assume name is already a formatted jQuery // object; otherwise, use match to format name for display. if (match) { _addHint = function (name) { var displayName = name.replace( new RegExp(StringUtils.regexEscape(match), "i"), "$&" ); view.hints.push({ formattedHint: "" + displayName + "" }); }; } else { _addHint = function (hint) { view.hints.push({ formattedHint: (hint.jquery) ? "" : hint }); }; } // clear the list this.$hintMenu.find("li").remove(); // if there are no hints then close the list; otherwise add them and // set the selection if (this.hints.length === 0) { if (this.handleClose) { this.handleClose(); } } else { this.hints.some(function (item, index) { if (index >= self.maxResults) { return true; } _addHint(item); }); // render code hint list var $ul = this.$hintMenu.find("ul.dropdown-menu"), $parent = $ul.parent(); // remove list temporarily to save rendering time $ul.remove().append(Mustache.render(CodeHintListHTML, view)); $ul.children("li").each(function (index, element) { var hint = self.hints[index], $element = $(element); // store hint on each list item $element.data("hint", hint); // insert jQuery hint objects after the template is rendered if (hint.jquery) { $element.find(".codehint-item").append(hint); } }); // delegate list item events to the top-level ul list element $ul.on("click", "li", function (e) { // Don't let the click propagate upward (otherwise it will // hit the close handler in bootstrap-dropdown). e.stopPropagation(); if (self.handleSelect) { self.handleSelect($(this).data("hint")); } }); // Lists with wide results require different formatting if (this.hints.handleWideResults) { $ul.find("li a").addClass("wide-result"); } // attach to DOM $parent.append($ul); // If a a description field requested attach one if (this.enableDescription) { // Remove the desc element first to ensure DOM order $parent.find("#codehint-desc").remove(); $parent.append(""); $ul.addClass("withDesc"); } this._setSelectedIndex(selectInitial ? 0 : -1); } }; /** * Computes top left location for hint list so that the list is not clipped by the window. * Also computes the largest available width. * * @private * @return {{left: number, top: number, width: number}} */ CodeHintList.prototype._calcHintListLocation = function () { var cursor = this.editor._codeMirror.cursorCoords(), posTop = cursor.bottom, posLeft = cursor.left, textHeight = this.editor.getTextHeight(), $window = $(window), $menuWindow = this.$hintMenu.children("ul"), $descElement = this.$hintMenu.find("#codehint-desc"), descOverhang = $descElement.length === 1 ? $descElement.height() : 0, menuHeight = $menuWindow.outerHeight() + descOverhang; // TODO Ty: factor out menu repositioning logic so code hints and Context menus share code // adjust positioning so menu is not clipped off bottom or right var bottomOverhang = posTop + menuHeight - $window.height(); if (bottomOverhang > 0) { posTop -= (textHeight + 2 + menuHeight); } posTop -= 30; // shift top for hidden parent element var menuWidth = $menuWindow.width(); var availableWidth = menuWidth; var rightOverhang = posLeft + menuWidth - $window.width(); if (rightOverhang > 0) { posLeft = Math.max(0, posLeft - rightOverhang); } else if (this.hints.handleWideResults) { // Right overhang is negative availableWidth = menuWidth + Math.abs(rightOverhang); } //Creating the offset element for hint description element var descOffset = this.$hintMenu.find("ul.dropdown-menu")[0].getBoundingClientRect().height; if (descOffset === 0) { descOffset = menuHeight - descOverhang; } this.$hintMenu.find("#codehint-desc").css("margin-top", descOffset - 1); return {left: posLeft, top: posTop, width: availableWidth}; }; /** * Check whether Event is one of the keys that we handle or not. * * @param {KeyBoardEvent|keyBoardEvent.keyCode} keyEvent */ CodeHintList.prototype.isHandlingKeyCode = function (keyCodeOrEvent) { var keyCode = typeof keyCodeOrEvent === "object" ? keyCodeOrEvent.keyCode : keyCodeOrEvent; var ctrlKey = typeof keyCodeOrEvent === "object" ? keyCodeOrEvent.ctrlKey : false; return (keyCode === KeyEvent.DOM_VK_UP || keyCode === KeyEvent.DOM_VK_DOWN || keyCode === KeyEvent.DOM_VK_PAGE_UP || keyCode === KeyEvent.DOM_VK_PAGE_DOWN || keyCode === KeyEvent.DOM_VK_RETURN || keyCode === KeyEvent.DOM_VK_CONTROL || keyCode === KeyEvent.DOM_VK_ESCAPE || (ctrlKey && keyCode === KeyEvent.DOM_VK_SPACE) || (keyCode === KeyEvent.DOM_VK_TAB && this.insertHintOnTab)); }; /** * Convert keydown events into hint list navigation actions. * * @param {KeyBoardEvent} keyEvent * @param {bool} isFakeKeydown - True if faked key down call (for example calling CTRL+Space while hints are open) */ CodeHintList.prototype._keydownHook = function (event, isFakeKeydown) { var keyCode, self = this; // positive distance rotates down; negative distance rotates up function _rotateSelection(distance) { var len = Math.min(self.hints.length, self.maxResults), pos; if (self.selectedIndex < 0) { // set the initial selection pos = (distance > 0) ? distance - 1 : len - 1; } else { // adjust current selection pos = self.selectedIndex; // Don't "rotate" until all items have been shown if (distance > 0) { if (pos === (len - 1)) { pos = 0; // wrap } else { pos = Math.min(pos + distance, len - 1); } } else { if (pos === 0) { pos = (len - 1); // wrap } else { pos = Math.max(pos + distance, 0); } } } self._setSelectedIndex(pos); } // Calculate the number of items per scroll page. function _itemsPerPage() { var itemsPerPage = 1, $items = self.$hintMenu.find("li"), $view = self.$hintMenu.find("ul.dropdown-menu"), itemHeight; if ($items.length !== 0) { itemHeight = $($items[0]).height(); if (itemHeight) { // round down to integer value itemsPerPage = Math.floor($view.height() / itemHeight); itemsPerPage = Math.max(1, Math.min(itemsPerPage, $items.length)); } } return itemsPerPage; } // If we're no longer visible, skip handling the key and end the session. if (!this.isOpen()) { this.handleClose(); return false; } // (page) up, (page) down, enter and tab key are handled by the list if ((event.type === "keydown" || isFakeKeydown) && this.isHandlingKeyCode(event)) { keyCode = event.keyCode; if (event.keyCode === KeyEvent.DOM_VK_ESCAPE) { event.stopImmediatePropagation(); this.handleClose(); return false; } else if (event.shiftKey && (event.keyCode === KeyEvent.DOM_VK_UP || event.keyCode === KeyEvent.DOM_VK_DOWN || event.keyCode === KeyEvent.DOM_VK_PAGE_UP || event.keyCode === KeyEvent.DOM_VK_PAGE_DOWN)) { this.handleClose(); // Let the event bubble. return false; } else if (keyCode === KeyEvent.DOM_VK_UP) { _rotateSelection.call(this, -1); } else if (keyCode === KeyEvent.DOM_VK_DOWN || (event.ctrlKey && keyCode === KeyEvent.DOM_VK_SPACE)) { _rotateSelection.call(this, 1); } else if (keyCode === KeyEvent.DOM_VK_PAGE_UP) { _rotateSelection.call(this, -_itemsPerPage()); } else if (keyCode === KeyEvent.DOM_VK_PAGE_DOWN) { _rotateSelection.call(this, _itemsPerPage()); } else if (this.selectedIndex !== -1 && (keyCode === KeyEvent.DOM_VK_RETURN || (keyCode === KeyEvent.DOM_VK_TAB && this.insertHintOnTab))) { if (this.pendingText) { // Issues #5003: We received a "selection" key while there is "pending // text". This is rare but can happen because CM uses polling, so we // can receive key events while CM is waiting for timeout to expire. // Pending text may dismiss the list, or it may cause a valid selection // which keeps open hint list. We can compare pending text against // list to determine whether list is dismissed or not, but to handle // inserting selection in the page we'd need to either: // 1. Synchronously force CodeMirror to poll (but there is not // yet a public API for that). // 2. Pass pending text back to where text gets inserted, which // means it would need to be implemented for every HintProvider! // You have to be typing so fast to hit this case, that's it's // highly unlikely that inserting something from list was the intent, // which makes this pretty rare, so case #2 is not worth implementing. // If case #1 gets implemented, then we may want to use it here. // So, assume that pending text dismisses hints and let event bubble. return false; } // Trigger a click handler to commmit the selected item $(this.$hintMenu.find("li")[this.selectedIndex]).trigger("click"); } else { // Let the event bubble. return false; } event.stopImmediatePropagation(); event.preventDefault(); return true; } // If we didn't handle it, let other global keydown hooks handle it. return false; }; /** * Is the CodeHintList open? * * @return {boolean} */ CodeHintList.prototype.isOpen = function () { // We don't get a notification when the dropdown closes. The best // we can do is keep an "opened" flag and check to see if we // still have the "open" class applied. if (this.opened && !this.$hintMenu.hasClass("open")) { this.opened = false; } return this.opened; }; /** * Displays the hint list at the current cursor position * * @param {{hints: Array., match: string, * selectInitial: boolean}} hintObj */ CodeHintList.prototype.open = function (hintObj) { Menus.closeAll(); this._buildListView(hintObj); if (this.hints.length) { // Need to add the menu to the DOM before trying to calculate its ideal location. $("#codehint-menu-bar > ul").append(this.$hintMenu); var hintPos = this._calcHintListLocation(); this.$hintMenu.addClass("open") .css({"left": hintPos.left, "top": hintPos.top, "width": hintPos.width + "px"}); this.opened = true; KeyBindingManager.addGlobalKeydownHook(this._keydownHook); } }; /** * Updates the (already open) hint list window with new hints * * @param {{hints: Array., match: string, * selectInitial: boolean}} hintObj */ CodeHintList.prototype.update = function (hintObj) { this.$hintMenu.addClass("apply-transition"); this._buildListView(hintObj); // Update the CodeHintList location if (this.hints.length) { var hintPos = this._calcHintListLocation(); this.$hintMenu.css({"left": hintPos.left, "top": hintPos.top, "width": hintPos.width + "px"}); } }; /** * Calls the move up keybind to move hint suggestion selector * * @param {KeyBoardEvent} keyEvent */ CodeHintList.prototype.callMoveUp = function (event) { this._keydownHook(event, true); }; /** * Closes the hint list */ CodeHintList.prototype.close = function () { this.opened = false; if (this.$hintMenu) { this.$hintMenu.removeClass("open"); PopUpManager.removePopUp(this.$hintMenu); this.$hintMenu.remove(); } KeyBindingManager.removeGlobalKeydownHook(this._keydownHook); }; /** * Set the hint list selection callback function * * @param {Function} callback */ CodeHintList.prototype.onSelect = function (callback) { this.handleSelect = callback; }; /** * Set the hint list highlight callback function * * @param {Function} callback */ CodeHintList.prototype.onHighlight = function (callback) { this.handleHighlight = callback; }; /** * Set the hint list closure callback function * * @param {Function} callback */ CodeHintList.prototype.onClose = function (callback) { // TODO: Due to #1381, this won't get called if the user clicks out of // the code hint menu. That's (sort of) okay right now since it doesn't // really matter if a single old invisible code hint list is lying // around (it will ignore keydown events, and it'll get closed the next // time the user pops up a code hint). Once #1381 is fixed this issue // should go away. this.handleClose = callback; }; // Define public API exports.CodeHintList = CodeHintList; }); ================================================ FILE: src/editor/CodeHintManager.js ================================================ /* * Copyright (c) 2012 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /* * __CodeHintManager Overview:__ * * The CodeHintManager mediates the interaction between the editor and a * collection of hint providers. If hints are requested explicitly by the * user, then the providers registered for the current language are queried * for their ability to provide hints in order of descending priority by * way their hasHints methods. Character insertions may also constitute an * implicit request for hints; consequently, providers for the current * language are also queried on character insertion for both their ability to * provide hints and also for the suitability of providing implicit hints * in the given editor context. * * Once a provider responds affirmatively to a request for hints, the * manager begins a hinting session with that provider, begins to query * that provider for hints by way of its getHints method, and opens the * hint list window. The hint list is kept open for the duration of the * current session. The manager maintains the session until either: * * 1. the provider gives a null response to a request for hints; * 2. a deferred response to getHints fails to resolve; * 3. the user explicitly dismisses the hint list window; * 4. the editor is closed or becomes inactive; or * 5. the editor undergoes a "complex" change, e.g., a multi-character * insertion, deletion or navigation. * * Single-character insertions, deletions or navigations may not * invalidate the current session; in which case, each such change * precipitates a successive call to getHints. * * If the user selects a hint from the rendered hint list then the * provider is responsible for inserting the hint into the editor context * for the current session by way of its insertHint method. The provider * may use the return value of insertHint to request that an additional * explicit hint request be triggered, potentially beginning a new * session. * * * __CodeHintProvider Overview:__ * * A code hint provider should implement the following three functions: * * - `CodeHintProvider.hasHints(editor, implicitChar)` * - `CodeHintProvider.getHints(implicitChar)` * - `CodeHintProvider.insertHint(hint)` * * The behavior of these three functions is described in detail below. * * __CodeHintProvider.hasHints(editor, implicitChar)__ * * The method by which a provider indicates intent to provide hints for a * given editor. The manager calls this method both when hints are * explicitly requested (via, e.g., Ctrl-Space) and when they may be * implicitly requested as a result of character insertion in the editor. * If the provider responds negatively then the manager may query other * providers for hints. Otherwise, a new hinting session begins with this * provider, during which the manager may repeatedly query the provider * for hints via the getHints method. Note that no other providers will be * queried until the hinting session ends. * * The implicitChar parameter is used to determine whether the hinting * request is explicit or implicit. If the string is null then hints were * explicitly requested and the provider should reply based on whether it * is possible to return hints for the given editor context. Otherwise, * the string contains just the last character inserted into the editor's * document and the request for hints is implicit. In this case, the * provider should determine whether it is both possible and appropriate * to show hints. Because implicit hints can be triggered by every * character insertion, hasHints may be called frequently; consequently, * the provider should endeavor to return a value as quickly as possible. * * Because calls to hasHints imply that a hinting session is about to * begin, a provider may wish to clean up cached data from previous * sessions in this method. Similarly, if the provider returns true, it * may wish to prepare to cache data suitable for the current session. In * particular, it should keep a reference to the editor object so that it * can access the editor in future calls to getHints and insertHints. * * param {Editor} editor * A non-null editor object for the active window. * * param {string} implicitChar * Either null, if the hinting request was explicit, or a single character * that represents the last insertion and that indicates an implicit * hinting request. * * return {boolean} * Determines whether the current provider is able to provide hints for * the given editor context and, in case implicitChar is non- null, * whether it is appropriate to do so. * * * __CodeHintProvider.getHints(implicitChar)__ * * The method by which a provider provides hints for the editor context * associated with the current session. The getHints method is called only * if the provider asserted its willingness to provide hints in an earlier * call to hasHints. The provider may return null or false, which indicates * that the manager should end the current hinting session and close the hint * list window; or true, which indicates that the manager should end the * current hinting session but immediately attempt to begin a new hinting * session by querying registered providers. Otherwise, the provider should * return a response object that contains the following properties: * * 1. hints, a sorted array hints that the provider could later insert * into the editor; * 2. match, a string that the manager may use to emphasize substrings of * hints in the hint list (case-insensitive); and * 3. selectInitial, a boolean that indicates whether or not the * first hint in the list should be selected by default. * 4. handleWideResults, a boolean (or undefined) that indicates whether * to allow result string to stretch width of display. * * If the array of * hints is empty, then the manager will render an empty list, but the * hinting session will remain open and the value of the selectInitial * property is irrelevant. * * Alternatively, the provider may return a jQuery.Deferred object * that resolves with an object with the structure described above. In * this case, the manager will initially render the hint list window with * a throbber and will render the actual list once the deferred object * resolves to a response object. If a hint list has already been rendered * (from an earlier call to getHints), then the old list will continue * to be displayed until the new deferred has resolved. * * Both the manager and the provider can reject the deferred object. The * manager will reject the deferred if the editor changes state (e.g., the * user types a character) or if the hinting session ends (e.g., the user * explicitly closes the hints by pressing escape). The provider can use * this event to, e.g., abort an expensive computation. Consequently, the * provider may assume that getHints will not be called again until the * deferred object from the current call has resolved or been rejected. If * the provider rejects the deferred, the manager will end the hinting * session. * * The getHints method may be called by the manager repeatedly during a * hinting session. Providers may wish to cache information for efficiency * that may be useful throughout these sessions. The same editor context * will be used throughout a session, and will only change during the * session as a result of single-character insertions, deletions and * cursor navigations. The provider may assume that, throughout the * lifetime of the session, the getHints method will be called exactly * once for each such editor change. Consequently, the provider may also * assume that the document will not be changed outside of the editor * during a session. * * param {string} implicitChar * Either null, if the request to update the hint list was a result of * navigation, or a single character that represents the last insertion. * * return {jQuery.Deferred|{ * hints: Array., * match: string, * selectInitial: boolean, * handleWideResults: boolean}} * * Null if the provider wishes to end the hinting session. Otherwise, a * response object, possibly deferred, that provides 1. a sorted array * hints that consists either of strings or jQuery objects; 2. a string * match, possibly null, that is used by the manager to emphasize * matching substrings when rendering the hint list; and 3. a boolean that * indicates whether the first result, if one exists, should be selected * by default in the hint list window. If match is non-null, then the * hints should be strings. * * If the match is null, the manager will not * attempt to emphasize any parts of the hints when rendering the hint * list; instead the provider may return strings or jQuery objects for * which emphasis is self-contained. For example, the strings may contain * substrings that wrapped in bold tags. In this way, the provider can * choose to let the manager handle emphasis for the simple and common case * of prefix matching, or can provide its own emphasis if it wishes to use * a more sophisticated matching algorithm. * * * __CodeHintProvider.insertHint(hint)__ * * The method by which a provider inserts a hint into the editor context * associated with the current session. The provider may assume that the * given hint was returned by the provider in some previous call in the * current session to getHints, but not necessarily the most recent call. * After the insertion has been performed, the current hinting session is * closed. The provider should return a boolean value to indicate whether * or not the end of the session should be immediately followed by a new * explicit hinting request, which may result in a new hinting session * being opened with some provider, but not necessarily the current one. * * param {string} hint * The hint to be inserted into the editor context for the current session. * * return {boolean} * Indicates whether the manager should follow hint insertion with an * explicit hint request. * * * __CodeHintProvider.insertHintOnTab__ * * type {?boolean} insertHintOnTab * Indicates whether the CodeHintManager should request that the provider of * the current session insert the currently selected hint on tab key events, * or if instead a tab character should be inserted into the editor. If omitted, * the fallback behavior is determined by the CodeHintManager. The default * behavior is to insert a tab character, but this can be changed with the * insertHintOnTab Preference. */ define(function (require, exports, module) { "use strict"; // Load dependent modules var Commands = require("command/Commands"), CommandManager = require("command/CommandManager"), EditorManager = require("editor/EditorManager"), Strings = require("strings"), KeyEvent = require("utils/KeyEvent"), CodeHintList = require("editor/CodeHintList").CodeHintList, PreferencesManager = require("preferences/PreferencesManager"); var hintProviders = { "all" : [] }, lastChar = null, sessionProvider = null, sessionEditor = null, hintList = null, deferredHints = null, keyDownEditor = null, codeHintsEnabled = true, codeHintOpened = false; PreferencesManager.definePreference("showCodeHints", "boolean", true, { description: Strings.DESCRIPTION_SHOW_CODE_HINTS }); PreferencesManager.definePreference("insertHintOnTab", "boolean", false, { description: Strings.DESCRIPTION_INSERT_HINT_ON_TAB }); PreferencesManager.definePreference("maxCodeHints", "number", 50, { description: Strings.DESCRIPTION_MAX_CODE_HINTS }); PreferencesManager.on("change", "showCodeHints", function () { codeHintsEnabled = PreferencesManager.get("showCodeHints"); }); /** * Comparator to sort providers from high to low priority */ function _providerSort(a, b) { return b.priority - a.priority; } /** * The method by which a CodeHintProvider registers its willingness to * providing hints for editors in a given language. * * @param {!CodeHintProvider} provider * The hint provider to be registered, described below. * * @param {!Array.} languageIds * The set of language ids for which the provider is capable of * providing hints. If the special language id name "all" is included then * the provider may be called for any language. * * @param {?number} priority * Used to break ties among hint providers for a particular language. * Providers with a higher number will be asked for hints before those * with a lower priority value. Defaults to zero. */ function registerHintProvider(providerInfo, languageIds, priority) { var providerObj = { provider: providerInfo, priority: priority || 0 }; if (languageIds.indexOf("all") !== -1) { // Ignore anything else in languageIds and just register for every language. This includes // the special "all" language since its key is in the hintProviders map from the beginning. var languageId; for (languageId in hintProviders) { if (hintProviders.hasOwnProperty(languageId)) { hintProviders[languageId].push(providerObj); hintProviders[languageId].sort(_providerSort); } } } else { languageIds.forEach(function (languageId) { if (!hintProviders[languageId]) { // Initialize provider list with any existing all-language providers hintProviders[languageId] = Array.prototype.concat(hintProviders.all); } hintProviders[languageId].push(providerObj); hintProviders[languageId].sort(_providerSort); }); } } /** * @private * Remove a code hint provider * @param {!CodeHintProvider} provider Code hint provider to remove * @param {(string|Array.)=} targetLanguageId Optional set of * language IDs for languages to remove the provider for. Defaults * to all languages. */ function _removeHintProvider(provider, targetLanguageId) { var index, providers, targetLanguageIdArr; if (Array.isArray(targetLanguageId)) { targetLanguageIdArr = targetLanguageId; } else if (targetLanguageId) { targetLanguageIdArr = [targetLanguageId]; } else { targetLanguageIdArr = Object.keys(hintProviders); } targetLanguageIdArr.forEach(function (languageId) { providers = hintProviders[languageId]; for (index = 0; index < providers.length; index++) { if (providers[index].provider === provider) { providers.splice(index, 1); break; } } }); } /** * Return the array of hint providers for the given language id. * This gets called (potentially) on every keypress. So, it should be fast. * * @param {!string} languageId * @return {?Array.<{provider: Object, priority: number}>} */ function _getProvidersForLanguageId(languageId) { var providers = hintProviders[languageId] || hintProviders.all; // Exclude providers that are explicitly disabled in the preferences. // All code hint providers that do not have their constructor // names listed in the preferences are enabled by default. return providers.filter(function (provider) { var prefKey = "codehint." + provider.provider.constructor.name; return PreferencesManager.get(prefKey) !== false; }); } var _beginSession; /** * End the current hinting session */ function _endSession() { if (!hintList) { return; } hintList.close(); hintList = null; codeHintOpened = false; keyDownEditor = null; sessionProvider = null; sessionEditor = null; if (deferredHints) { deferredHints.reject(); deferredHints = null; } } /** * Is there a hinting session active for a given editor? * * NOTE: the sessionEditor, sessionProvider and hintList objects are * only guaranteed to be initialized during an active session. * * @param {Editor} editor * @return boolean */ function _inSession(editor) { if (sessionEditor) { if (sessionEditor === editor && (hintList.isOpen() || (deferredHints && deferredHints.state() === "pending"))) { return true; } else { // the editor has changed _endSession(); } } return false; } /** * From an active hinting session, get hints from the current provider and * render the hint list window. * * Assumes that it is called when a session is active (i.e. sessionProvider is not null). */ function _updateHintList(callMoveUpEvent) { callMoveUpEvent = typeof callMoveUpEvent === "undefined" ? false : callMoveUpEvent; if (deferredHints) { deferredHints.reject(); deferredHints = null; } if (callMoveUpEvent) { return hintList.callMoveUp(callMoveUpEvent); } var response = sessionProvider.getHints(lastChar); lastChar = null; if (!response) { // the provider wishes to close the session _endSession(); } else { // if the response is true, end the session and begin another if (response === true) { var previousEditor = sessionEditor; _endSession(); _beginSession(previousEditor); } else if (response.hasOwnProperty("hints")) { // a synchronous response if (hintList.isOpen()) { // the session is open hintList.update(response); } else { hintList.open(response); } } else { // response is a deferred deferredHints = response; response.done(function (hints) { // Guard against timing issues where the session ends before the // response gets a chance to execute the callback. If the session // ends first while still waiting on the response, then hintList // will get cleared up. if (!hintList) { return; } if (hintList.isOpen()) { // the session is open hintList.update(hints); } else { hintList.open(hints); } }); } } } /** * Try to begin a new hinting session. * @param {Editor} editor */ _beginSession = function (editor) { if (!codeHintsEnabled) { return; } // Don't start a session if we have a multiple selection. if (editor.getSelections().length > 1) { return; } // Find a suitable provider, if any var language = editor.getLanguageForSelection(), enabledProviders = _getProvidersForLanguageId(language.getId()); enabledProviders.some(function (item, index) { if (item.provider.hasHints(editor, lastChar)) { sessionProvider = item.provider; return true; } }); // If a provider is found, initialize the hint list and update it if (sessionProvider) { var insertHintOnTab, maxCodeHints = PreferencesManager.get("maxCodeHints"); if (sessionProvider.insertHintOnTab !== undefined) { insertHintOnTab = sessionProvider.insertHintOnTab; } else { insertHintOnTab = PreferencesManager.get("insertHintOnTab"); } sessionEditor = editor; hintList = new CodeHintList(sessionEditor, insertHintOnTab, maxCodeHints); hintList.onHighlight(function ($hint, $hintDescContainer) { if (hintList.enableDescription && $hintDescContainer && $hintDescContainer.length) { // If the current hint provider listening for hint item highlight change if (sessionProvider.onHighlight) { sessionProvider.onHighlight($hint, $hintDescContainer); } // Update the hint description if (sessionProvider.updateHintDescription) { sessionProvider.updateHintDescription($hint, $hintDescContainer); } } else { if (sessionProvider.onHighlight) { sessionProvider.onHighlight($hint); } } }); hintList.onSelect(function (hint) { var restart = sessionProvider.insertHint(hint), previousEditor = sessionEditor; _endSession(); if (restart) { _beginSession(previousEditor); } }); hintList.onClose(_endSession); _updateHintList(); } else { lastChar = null; } }; /** * Handles keys related to displaying, searching, and navigating the hint list. * This gets called before handleChange. * * TODO: Ideally, we'd get a more semantic event from the editor that told us * what changed so that we could do all of this logic without looking at * key events. Then, the purposes of handleKeyEvent and handleChange could be * combined. Doing this well requires changing CodeMirror. * * @param {Event} jqEvent * @param {Editor} editor * @param {KeyboardEvent} event */ function _handleKeydownEvent(jqEvent, editor, event) { keyDownEditor = editor; if (!(event.ctrlKey || event.altKey || event.metaKey) && (event.keyCode === KeyEvent.DOM_VK_ENTER || event.keyCode === KeyEvent.DOM_VK_RETURN || event.keyCode === KeyEvent.DOM_VK_TAB)) { lastChar = String.fromCharCode(event.keyCode); } } function _handleKeypressEvent(jqEvent, editor, event) { keyDownEditor = editor; // Last inserted character, used later by handleChange lastChar = String.fromCharCode(event.charCode); // Pending Text is used in hintList._keydownHook() if (hintList) { hintList.addPendingText(lastChar); } } function _handleKeyupEvent(jqEvent, editor, event) { keyDownEditor = editor; if (_inSession(editor)) { if (event.keyCode === KeyEvent.DOM_VK_HOME || event.keyCode === KeyEvent.DOM_VK_END) { _endSession(); } else if (event.keyCode === KeyEvent.DOM_VK_LEFT || event.keyCode === KeyEvent.DOM_VK_RIGHT || event.keyCode === KeyEvent.DOM_VK_BACK_SPACE) { // Update the list after a simple navigation. // We do this in "keyup" because we want the cursor position to be updated before // we redraw the list. _updateHintList(); } else if (event.ctrlKey && event.keyCode === KeyEvent.DOM_VK_SPACE) { _updateHintList(event); } } } /** * Handle a selection change event in the editor. If the selection becomes a * multiple selection, end our current session. * @param {BracketsEvent} event * @param {Editor} editor */ function _handleCursorActivity(event, editor) { if (_inSession(editor)) { if (editor.getSelections().length > 1) { _endSession(); } } } /** * Start a new implicit hinting session, or update the existing hint list. * Called by the editor after handleKeyEvent, which is responsible for setting * the lastChar. * * @param {Event} event * @param {Editor} editor * @param {{from: Pos, to: Pos, text: Array, origin: string}} changeList */ function _handleChange(event, editor, changeList) { if (lastChar && editor === keyDownEditor) { keyDownEditor = null; if (_inSession(editor)) { var charToRetest = lastChar; _updateHintList(); // _updateHintList() may end a hinting session and clear lastChar, but a // different provider may want to start a new session with the same character. // So check whether current provider terminates the current hinting // session. If so, then restore lastChar and restart a new session. if (!_inSession(editor)) { lastChar = charToRetest; _beginSession(editor); } } else { _beginSession(editor); } // Pending Text is used in hintList._keydownHook() if (hintList && changeList[0] && changeList[0].text.length && changeList[0].text[0].length) { var expectedLength = editor.getCursorPos().ch - changeList[0].from.ch, newText = changeList[0].text[0]; // We may get extra text in newText since some features like auto // close braces can append some text automatically. // See https://github.com/adobe/brackets/issues/6345#issuecomment-32548064 // as an example of this scenario. if (newText.length > expectedLength) { // Strip off the extra text before calling removePendingText. newText = newText.substr(0, expectedLength); } hintList.removePendingText(newText); } } } /** * Test whether the provider has an exclusion that is still the same as text after the cursor. * * @param {string} exclusion - Text not to be overwritten when the provider inserts the selected hint. * @param {string} textAfterCursor - Text that is immediately after the cursor position. * @return {boolean} true if the exclusion is not null and is exactly the same as textAfterCursor, * false otherwise. */ function hasValidExclusion(exclusion, textAfterCursor) { return (exclusion && exclusion === textAfterCursor); } /** * Test if a hint popup is open. * * @return {boolean} - true if the hints are open, false otherwise. */ function isOpen() { return (hintList && hintList.isOpen()); } /** * Explicitly start a new session. If we have an existing session, * then close the current one and restart a new one. * @param {Editor} editor */ function _startNewSession(editor) { if (isOpen()) { return; } if (!editor) { editor = EditorManager.getFocusedEditor(); } if (editor) { lastChar = null; if (_inSession(editor)) { _endSession(); } // Begin a new explicit session _beginSession(editor); } } /** * Expose CodeHintList for unit testing */ function _getCodeHintList() { return hintList; } function activeEditorChangeHandler(event, current, previous) { if (current) { current.on("editorChange", _handleChange); current.on("keydown", _handleKeydownEvent); current.on("keypress", _handleKeypressEvent); current.on("keyup", _handleKeyupEvent); current.on("cursorActivity", _handleCursorActivity); } if (previous) { //Removing all old Handlers previous.off("editorChange", _handleChange); previous.off("keydown", _handleKeydownEvent); previous.off("keypress", _handleKeypressEvent); previous.off("keyup", _handleKeyupEvent); previous.off("cursorActivity", _handleCursorActivity); } } activeEditorChangeHandler(null, EditorManager.getActiveEditor(), null); EditorManager.on("activeEditorChange", activeEditorChangeHandler); // Dismiss code hints before executing any command other than showing code hints since the command // may make the current hinting session irrevalent after execution. // For example, when the user hits Ctrl+K to open Quick Doc, it is // pointless to keep the hint list since the user wants to view the Quick Doc CommandManager.on("beforeExecuteCommand", function (event, commandId) { if (commandId !== Commands.SHOW_CODE_HINTS) { _endSession(); } }); CommandManager.register(Strings.CMD_SHOW_CODE_HINTS, Commands.SHOW_CODE_HINTS, _startNewSession); exports._getCodeHintList = _getCodeHintList; exports._removeHintProvider = _removeHintProvider; // Define public API exports.isOpen = isOpen; exports.registerHintProvider = registerHintProvider; exports.hasValidExclusion = hasValidExclusion; }); ================================================ FILE: src/editor/Editor.js ================================================ /* * Copyright (c) 2012 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /** * Editor is a 1-to-1 wrapper for a CodeMirror editor instance. It layers on Brackets-specific * functionality and provides APIs that cleanly pass through the bits of CodeMirror that the rest * of our codebase may want to interact with. An Editor is always backed by a Document, and stays * in sync with its content; because Editor keeps the Document alive, it's important to always * destroy() an Editor that's going away so it can release its Document ref. * * For now, there's a distinction between the "master" Editor for a Document - which secretly acts * as the Document's internal model of the text state - and the multitude of "slave" secondary Editors * which, via Document, sync their changes to and from that master. * * For now, direct access to the underlying CodeMirror object is still possible via `_codeMirror` -- * but this is considered deprecated and may go away. * * The Editor object dispatches the following events: * - keydown, keypress, keyup -- When any key event happens in the editor (whether it changes the * text or not). Handlers are passed `(BracketsEvent, Editor, KeyboardEvent)`. The 3nd arg is the * raw DOM event. Note: most listeners will only want to listen for "keypress". * - cursorActivity -- When the user moves the cursor or changes the selection, or an edit occurs. * Note: do not listen to this in order to be generally informed of edits--listen to the * "change" event on Document instead. * - scroll -- When the editor is scrolled, either by user action or programmatically. * - lostContent -- When the backing Document changes in such a way that this Editor is no longer * able to display accurate text. This occurs if the Document's file is deleted, or in certain * Document->editor syncing edge cases that we do not yet support (the latter cause will * eventually go away). * - optionChange -- Triggered when an option for the editor is changed. The 2nd arg to the listener * is a string containing the editor option that is changing. The 3rd arg, which can be any * data type, is the new value for the editor option. * - beforeDestroy - Triggered before the object is about to dispose of all its internal state data * so that listeners can cache things like scroll pos, etc... * * The Editor also dispatches "change" events internally, but you should listen for those on * Documents, not Editors. * * To listen for events, do something like this: (see EventDispatcher for details on this pattern) * `editorInstance.on("eventname", handler);` */ define(function (require, exports, module) { "use strict"; var AnimationUtils = require("utils/AnimationUtils"), Async = require("utils/Async"), CodeMirror = require("thirdparty/CodeMirror/lib/codemirror"), LanguageManager = require("language/LanguageManager"), EventDispatcher = require("utils/EventDispatcher"), Menus = require("command/Menus"), PerfUtils = require("utils/PerfUtils"), PopUpManager = require("widgets/PopUpManager"), PreferencesManager = require("preferences/PreferencesManager"), Strings = require("strings"), TextRange = require("document/TextRange").TextRange, TokenUtils = require("utils/TokenUtils"), ValidationUtils = require("utils/ValidationUtils"), HTMLUtils = require("language/HTMLUtils"), ViewUtils = require("utils/ViewUtils"), MainViewManager = require("view/MainViewManager"), _ = require("thirdparty/lodash"); /** Editor preferences */ var CLOSE_BRACKETS = "closeBrackets", CLOSE_TAGS = "closeTags", DRAG_DROP = "dragDropText", HIGHLIGHT_MATCHES = "highlightMatches", LINEWISE_COPY_CUT = "lineWiseCopyCut", SCROLL_PAST_END = "scrollPastEnd", SHOW_CURSOR_SELECT = "showCursorWhenSelecting", SHOW_LINE_NUMBERS = "showLineNumbers", SMART_INDENT = "smartIndent", SOFT_TABS = "softTabs", SPACE_UNITS = "spaceUnits", STYLE_ACTIVE_LINE = "styleActiveLine", TAB_SIZE = "tabSize", UPPERCASE_COLORS = "uppercaseColors", USE_TAB_CHAR = "useTabChar", WORD_WRAP = "wordWrap", AUTO_HIDE_SEARCH = "autoHideSearch", INDENT_LINE_COMMENT = "indentLineComment", INDENT_LINE_COMMENT = "indentLineComment", INPUT_STYLE = "inputStyle"; /** * A list of gutter name and priorities currently registered for editors. * The line number gutter is defined as { name: LINE_NUMBER_GUTTER, priority: 100 } * @type {Array.<{name: string, priority: number, languageIds: Array}} */ var registeredGutters = []; var cmOptions = {}; /** * Constants * @type {number} */ var MIN_SPACE_UNITS = 1, MIN_TAB_SIZE = 1, DEFAULT_SPACE_UNITS = 4, DEFAULT_TAB_SIZE = 4, MAX_SPACE_UNITS = 10, MAX_TAB_SIZE = 10; var LINE_NUMBER_GUTTER = "CodeMirror-linenumbers", LINE_NUMBER_GUTTER_PRIORITY = 100, CODE_FOLDING_GUTTER_PRIORITY = 1000; // Mappings from Brackets preferences to CodeMirror options cmOptions[CLOSE_BRACKETS] = "autoCloseBrackets"; cmOptions[CLOSE_TAGS] = "autoCloseTags"; cmOptions[DRAG_DROP] = "dragDrop"; cmOptions[HIGHLIGHT_MATCHES] = "highlightSelectionMatches"; cmOptions[LINEWISE_COPY_CUT] = "lineWiseCopyCut"; cmOptions[SCROLL_PAST_END] = "scrollPastEnd"; cmOptions[SHOW_CURSOR_SELECT] = "showCursorWhenSelecting"; cmOptions[SHOW_LINE_NUMBERS] = "lineNumbers"; cmOptions[SMART_INDENT] = "smartIndent"; cmOptions[SPACE_UNITS] = "indentUnit"; cmOptions[STYLE_ACTIVE_LINE] = "styleActiveLine"; cmOptions[TAB_SIZE] = "tabSize"; cmOptions[USE_TAB_CHAR] = "indentWithTabs"; cmOptions[WORD_WRAP] = "lineWrapping"; cmOptions[INPUT_STYLE] = "inputStyle"; PreferencesManager.definePreference(CLOSE_BRACKETS, "boolean", true, { description: Strings.DESCRIPTION_CLOSE_BRACKETS }); // CodeMirror, html mode, set some tags do not close automatically. // We do not initialize "dontCloseTags" because otherwise we would overwrite the default behavior of CodeMirror. PreferencesManager.definePreference(CLOSE_TAGS, "object", { whenOpening: true, whenClosing: true, indentTags: [] }, { description: Strings.DESCRIPTION_CLOSE_TAGS, keys: { dontCloseTags: { type: "array", description: Strings.DESCRIPTION_CLOSE_TAGS_DONT_CLOSE_TAGS }, whenOpening: { type: "boolean", description: Strings.DESCRIPTION_CLOSE_TAGS_WHEN_OPENING, initial: true }, whenClosing: { type: "boolean", description: Strings.DESCRIPTION_CLOSE_TAGS_WHEN_CLOSING, initial: true }, indentTags: { type: "array", description: Strings.DESCRIPTION_CLOSE_TAGS_INDENT_TAGS } } }); PreferencesManager.definePreference(DRAG_DROP, "boolean", false, { description: Strings.DESCRIPTION_DRAG_DROP_TEXT }); PreferencesManager.definePreference(HIGHLIGHT_MATCHES, "boolean", false, { description: Strings.DESCRIPTION_HIGHLIGHT_MATCHES, keys: { showToken: { type: "boolean", description: Strings.DESCRIPTION_HIGHLIGHT_MATCHES_SHOW_TOKEN, initial: false }, wordsOnly: { type: "boolean", description: Strings.DESCRIPTION_HIGHLIGHT_MATCHES_WORDS_ONLY, initial: false } } }); PreferencesManager.definePreference(LINEWISE_COPY_CUT, "boolean", true, { description: Strings.DESCRIPTION_LINEWISE_COPY_CUT }); PreferencesManager.definePreference(SCROLL_PAST_END, "boolean", false, { description: Strings.DESCRIPTION_SCROLL_PAST_END }); PreferencesManager.definePreference(SHOW_CURSOR_SELECT, "boolean", false, { description: Strings.DESCRIPTION_SHOW_CURSOR_WHEN_SELECTING }); PreferencesManager.definePreference(SHOW_LINE_NUMBERS, "boolean", true, { description: Strings.DESCRIPTION_SHOW_LINE_NUMBERS }); PreferencesManager.definePreference(SMART_INDENT, "boolean", true, { description: Strings.DESCRIPTION_SMART_INDENT }); PreferencesManager.definePreference(SOFT_TABS, "boolean", true, { description: Strings.DESCRIPTION_SOFT_TABS }); PreferencesManager.definePreference(SPACE_UNITS, "number", DEFAULT_SPACE_UNITS, { validator: _.partialRight(ValidationUtils.isIntegerInRange, MIN_SPACE_UNITS, MAX_SPACE_UNITS), description: Strings.DESCRIPTION_SPACE_UNITS }); PreferencesManager.definePreference(STYLE_ACTIVE_LINE, "boolean", false, { description: Strings.DESCRIPTION_STYLE_ACTIVE_LINE }); PreferencesManager.definePreference(TAB_SIZE, "number", DEFAULT_TAB_SIZE, { validator: _.partialRight(ValidationUtils.isIntegerInRange, MIN_TAB_SIZE, MAX_TAB_SIZE), description: Strings.DESCRIPTION_TAB_SIZE }); PreferencesManager.definePreference(UPPERCASE_COLORS, "boolean", false, { description: Strings.DESCRIPTION_UPPERCASE_COLORS }); PreferencesManager.definePreference(USE_TAB_CHAR, "boolean", false, { description: Strings.DESCRIPTION_USE_TAB_CHAR }); PreferencesManager.definePreference(WORD_WRAP, "boolean", true, { description: Strings.DESCRIPTION_WORD_WRAP }); PreferencesManager.definePreference(AUTO_HIDE_SEARCH, "boolean", true, { description: Strings.DESCRIPTION_SEARCH_AUTOHIDE }); PreferencesManager.definePreference(INDENT_LINE_COMMENT, "boolean", false, { description: Strings.DESCRIPTION_INDENT_LINE_COMMENT }); PreferencesManager.definePreference(INPUT_STYLE, "string", "textarea", { description: Strings.DESCRIPTION_INPUT_STYLE }); var editorOptions = Object.keys(cmOptions); /** Editor preferences */ /** * Guard flag to prevent focus() reentrancy (via blur handlers), even across Editors * @type {boolean} */ var _duringFocus = false; /** * Constant: ignore upper boundary when centering text * @type {number} */ var BOUNDARY_CHECK_NORMAL = 0, BOUNDARY_IGNORE_TOP = 1; /** * @private * Create a copy of the given CodeMirror position * @param {!CodeMirror.Pos} pos * @return {CodeMirror.Pos} */ function _copyPos(pos) { return new CodeMirror.Pos(pos.line, pos.ch); } /** * Helper functions to check options. * @param {number} options BOUNDARY_CHECK_NORMAL or BOUNDARY_IGNORE_TOP */ function _checkTopBoundary(options) { return (options !== BOUNDARY_IGNORE_TOP); } function _checkBottomBoundary(options) { return true; } /** * Helper function to build preferences context based on the full path of * the file. * * @param {string} fullPath Full path of the file * * @return {*} A context for the specified file name */ function _buildPreferencesContext(fullPath) { return PreferencesManager._buildContext(fullPath, fullPath ? LanguageManager.getLanguageForPath(fullPath).getId() : undefined); } /** * List of all current (non-destroy()ed) Editor instances. Needed when changing global preferences * that affect all editors, e.g. tabbing or color scheme settings. * @type {Array.} */ var _instances = []; /** * Creates a new CodeMirror editor instance bound to the given Document. The Document need not have * a "master" Editor realized yet, even if makeMasterEditor is false; in that case, the first time * an edit occurs we will automatically ask EditorManager to create a "master" editor to render the * Document modifiable. * * ALWAYS call destroy() when you are done with an Editor - otherwise it will leak a Document ref. * * @constructor * * @param {!Document} document * @param {!boolean} makeMasterEditor If true, this Editor will set itself as the (secret) "master" * Editor for the Document. If false, this Editor will attach to the Document as a "slave"/ * secondary editor. * @param {!jQueryObject|DomNode} container Container to add the editor to. * @param {{startLine: number, endLine: number}=} range If specified, range of lines within the document * to display in this editor. Inclusive. * @param {!Object} options If specified, contains editor options that can be passed to CodeMirror */ function Editor(document, makeMasterEditor, container, range, options) { var self = this; var isReadOnly = (options && options.isReadOnly) || !document.editable; _instances.push(this); // Attach to document: add ref & handlers this.document = document; document.addRef(); if (container.jquery) { // CodeMirror wants a DOM element, not a jQuery wrapper container = container.get(0); } var $container = $(container); if (range) { // attach this first: want range updated before we process a change this._visibleRange = new TextRange(document, range.startLine, range.endLine); } // store this-bound version of listeners so we can remove them later this._handleDocumentChange = this._handleDocumentChange.bind(this); this._handleDocumentDeleted = this._handleDocumentDeleted.bind(this); this._handleDocumentLanguageChanged = this._handleDocumentLanguageChanged.bind(this); this._doWorkingSetSync = this._doWorkingSetSync.bind(this); document.on("change", this._handleDocumentChange); document.on("deleted", this._handleDocumentDeleted); document.on("languageChanged", this._handleDocumentLanguageChanged); // To sync working sets if the view is for same doc across panes document.on("_dirtyFlagChange", this._doWorkingSetSync); var mode = this._getModeFromDocument(); // (if makeMasterEditor, we attach the Doc back to ourselves below once we're fully initialized) this._inlineWidgets = []; this._inlineWidgetQueues = {}; this._hideMarks = []; this._lastEditorWidth = null; this._$messagePopover = null; // To track which pane the editor is being attached to if it's a full editor this._paneId = null; // To track the parent editor ( host editor at that time of creation) of an inline editor this._hostEditor = null; // Editor supplies some standard keyboard behavior extensions of its own var codeMirrorKeyMap = { "Tab": function () { self._handleTabKey(); }, "Shift-Tab": "indentLess", "Left": function (instance) { self._handleSoftTabNavigation(-1, "moveH"); }, "Right": function (instance) { self._handleSoftTabNavigation(1, "moveH"); }, "Backspace": function (instance) { self._handleSoftTabNavigation(-1, "deleteH"); }, "Delete": function (instance) { self._handleSoftTabNavigation(1, "deleteH"); }, "Esc": function (instance) { if (self.getSelections().length > 1) { CodeMirror.commands.singleSelection(instance); } else { self.removeAllInlineWidgets(); } }, "Home": "goLineLeftSmart", "Cmd-Left": "goLineLeftSmart", "End": "goLineRight", "Cmd-Right": "goLineRight" }; var currentOptions = this._currentOptions = _.zipObject( editorOptions, _.map(editorOptions, function (prefName) { return self._getOption(prefName); }) ); // When panes are created *after* the showLineNumbers option has been turned off // we need to apply the show-line-padding class or the text will be juxtaposed // to the edge of the editor which makes it not easy to read. The code below to handle // that the option change only applies the class to panes that have already been created // This line ensures that the class is applied to any editor created after the fact $container.toggleClass("show-line-padding", Boolean(!this._getOption("showLineNumbers"))); // Create the CodeMirror instance // (note: CodeMirror doesn't actually require using 'new', but jslint complains without it) this._codeMirror = new CodeMirror(container, { autoCloseBrackets : currentOptions[CLOSE_BRACKETS], autoCloseTags : currentOptions[CLOSE_TAGS], coverGutterNextToScrollbar : true, cursorScrollMargin : 3, dragDrop : currentOptions[DRAG_DROP], electricChars : true, extraKeys : codeMirrorKeyMap, highlightSelectionMatches : currentOptions[HIGHLIGHT_MATCHES], indentUnit : currentOptions[USE_TAB_CHAR] ? currentOptions[TAB_SIZE] : currentOptions[SPACE_UNITS], indentWithTabs : currentOptions[USE_TAB_CHAR], inputStyle : currentOptions[INPUT_STYLE], lineNumbers : currentOptions[SHOW_LINE_NUMBERS], lineWiseCopyCut : currentOptions[LINEWISE_COPY_CUT], lineWrapping : currentOptions[WORD_WRAP], matchBrackets : { maxScanLineLength: 50000, maxScanLines: 1000 }, matchTags : { bothTags: true }, scrollPastEnd : !range && currentOptions[SCROLL_PAST_END], showCursorWhenSelecting : currentOptions[SHOW_CURSOR_SELECT], smartIndent : currentOptions[SMART_INDENT], styleActiveLine : currentOptions[STYLE_ACTIVE_LINE], tabSize : currentOptions[TAB_SIZE], readOnly : isReadOnly }); // Can't get CodeMirror's focused state without searching for // CodeMirror-focused. Instead, track focus via onFocus and onBlur // options and track state with this._focused this._focused = false; this._installEditorListeners(); this._renderGutters(); this.on("cursorActivity", function (event, editor) { self._handleCursorActivity(event); }); this.on("keypress", function (event, editor, domEvent) { self._handleKeypressEvents(domEvent); }); this.on("change", function (event, editor, changeList) { self._handleEditorChange(changeList); }); this.on("focus", function (event, editor) { if (self._hostEditor) { // Mark the host editor as the master editor for the hosting document self._hostEditor.document._toggleMasterEditor(self._hostEditor); } else { // Set this full editor as master editor for the document self.document._toggleMasterEditor(self); } }); // Set code-coloring mode BEFORE populating with text, to avoid a flash of uncolored text this._codeMirror.setOption("mode", mode); // Initially populate with text. This will send a spurious change event, so need to make // sure this is understood as a 'sync from document' case, not a genuine edit this._duringSync = true; this._resetText(document.getText()); this._duringSync = false; if (range) { this._updateHiddenLines(); this.setCursorPos(range.startLine, 0); } // Now that we're fully initialized, we can point the document back at us if needed if (makeMasterEditor) { document._makeEditable(this); } // Add scrollTop property to this object for the scroll shadow code to use Object.defineProperty(this, "scrollTop", { get: function () { return this._codeMirror.getScrollInfo().top; } }); // Add an $el getter for Pane Views Object.defineProperty(this, "$el", { get: function () { return $(this.getRootElement()); } }); } EventDispatcher.makeEventDispatcher(Editor.prototype); EventDispatcher.markDeprecated(Editor.prototype, "keyEvent", "'keydown/press/up'"); Editor.prototype.markPaneId = function (paneId) { this._paneId = paneId; // Also add this to the pool of full editors this.document._associateEditor(this); // In case this Editor is initialized not as the first full editor for the document // and the document is already dirty and present in another working set, make sure // to add this documents to the new panes working set. this._doWorkingSetSync(null, this.document); }; Editor.prototype._doWorkingSetSync = function (event, doc) { if (doc === this.document && this._paneId && this.document.isDirty) { MainViewManager.addToWorkingSet(this._paneId, this.document.file, -1, false); } }; /** * Removes this editor from the DOM and detaches from the Document. If this is the "master" * Editor that is secretly providing the Document's backing state, then the Document reverts to * a read-only string-backed mode. */ Editor.prototype.destroy = function () { this.trigger("beforeDestroy", this); // CodeMirror docs for getWrapperElement() say all you have to do is "Remove this from your // tree to delete an editor instance." $(this.getRootElement()).remove(); _instances.splice(_instances.indexOf(this), 1); // Disconnect from Document this.document.releaseRef(); this.document.off("change", this._handleDocumentChange); this.document.off("deleted", this._handleDocumentDeleted); this.document.off("languageChanged", this._handleDocumentLanguageChanged); this.document.off("_dirtyFlagChange", this._doWorkingSetSync); if (this._visibleRange) { // TextRange also refs the Document this._visibleRange.dispose(); } // If we're the Document's master editor, disconnecting from it has special meaning if (this.document._masterEditor === this) { this.document._makeNonEditable(); } else { this.document._disassociateEditor(this); } // Destroying us destroys any inline widgets we're hosting. Make sure their closeCallbacks // run, at least, since they may also need to release Document refs var self = this; this._inlineWidgets.forEach(function (inlineWidget) { self._removeInlineWidgetInternal(inlineWidget); }); }; /** * @private * Handle any cursor movement in editor, including selecting and unselecting text. * @param {!Event} event */ Editor.prototype._handleCursorActivity = function (event) { this._updateStyleActiveLine(); }; /** * @private * Removes any whitespace after one of ]{}) to prevent trailing whitespace when auto-indenting */ Editor.prototype._handleWhitespaceForElectricChars = function () { var self = this, instance = this._codeMirror, selections, lineStr; selections = this.getSelections().map(function (sel) { lineStr = instance.getLine(sel.end.line); if (lineStr && !/\S/.test(lineStr)) { // if the line is all whitespace, move the cursor to the end of the line // before indenting so that embedded whitespace such as indents are not // orphaned to the right of the electric char being inserted sel.end.ch = self.document.getLine(sel.end.line).length; } return sel; }); this.setSelections(selections); }; /** * @private * Handle CodeMirror key events. * @param {!Event} event */ Editor.prototype._handleKeypressEvents = function (event) { var keyStr = String.fromCharCode(event.which || event.keyCode); if (/[\]\{\}\)]/.test(keyStr)) { this._handleWhitespaceForElectricChars(); } }; /** * @private * Helper function for `_handleTabKey()` (case 2) - see comment in that function. * @param {Array.<{start:{line:number, ch:number}, end:{line:number, ch:number}, reversed:boolean, primary:boolean}>} selections * The selections to indent. */ Editor.prototype._addIndentAtEachSelection = function (selections) { var instance = this._codeMirror, usingTabs = instance.getOption("indentWithTabs"), indentUnit = instance.getOption("indentUnit"), edits = []; _.each(selections, function (sel) { var indentStr = "", i, numSpaces; if (usingTabs) { indentStr = "\t"; } else { numSpaces = indentUnit - (sel.start.ch % indentUnit); for (i = 0; i < numSpaces; i++) { indentStr += " "; } } edits.push({edit: {text: indentStr, start: sel.start}}); }); this.document.doMultipleEdits(edits); }; /** * @private * Helper function for `_handleTabKey()` (case 3) - see comment in that function. * @param {Array.<{start:{line:number, ch:number}, end:{line:number, ch:number}, reversed:boolean, primary:boolean}>} selections * The selections to indent. */ Editor.prototype._autoIndentEachSelection = function (selections) { // Capture all the line lengths, so we can tell if anything changed. // Note that this function should only be called if all selections are within a single line. var instance = this._codeMirror, lineLengths = {}; _.each(selections, function (sel) { lineLengths[sel.start.line] = instance.getLine(sel.start.line).length; }); // First, try to do a smart indent on all selections. CodeMirror.commands.indentAuto(instance); // If there were no code or selection changes, then indent each selection one more indent. var changed = false, newSelections = this.getSelections(); if (newSelections.length === selections.length) { _.each(selections, function (sel, index) { var newSel = newSelections[index]; if (CodeMirror.cmpPos(sel.start, newSel.start) !== 0 || CodeMirror.cmpPos(sel.end, newSel.end) !== 0 || instance.getLine(sel.start.line).length !== lineLengths[sel.start.line]) { changed = true; // Bail - we don't need to look any further once we've found a change. return false; } }); } else { changed = true; } if (!changed) { CodeMirror.commands.indentMore(instance); } }; /** * @private * Handle Tab key press. */ Editor.prototype._handleTabKey = function () { // Tab key handling is done as follows: // 1. If any of the selections are multiline, just add one indent level to the // beginning of all lines that intersect any selection. // 2. Otherwise, if any of the selections is a cursor or single-line range that // ends at or after the first non-whitespace character in a line: // - if indentation is set to tabs, just insert a hard tab before each selection. // - if indentation is set to spaces, insert the appropriate number of spaces before // each selection to get to its next soft tab stop. // 3. Otherwise (all selections are cursors or single-line, and are in the whitespace // before their respective lines), try to autoindent each line based on the mode. // If none of the cursors moved and no space was added, then add one indent level // to the beginning of all lines. // Note that in case 2, we do the "dumb" insertion even if the cursor is immediately // before the first non-whitespace character in a line. It might seem more convenient // to do autoindent in that case. However, the problem is if that line is already // indented past its "proper" location. In that case, we don't want Tab to // *outdent* the line. If we had more control over the autoindent algorithm or // implemented it ourselves, we could handle that case separately. var instance = this._codeMirror, selectionType = "indentAuto", selections = this.getSelections(); _.each(selections, function (sel) { if (sel.start.line !== sel.end.line) { // Case 1 - we found a multiline selection. We can bail as soon as we find one of these. selectionType = "indentAtBeginning"; return false; } else if (sel.end.ch > 0 && sel.end.ch >= instance.getLine(sel.end.line).search(/\S/)) { // Case 2 - we found a selection that ends at or after the first non-whitespace // character on the line. We need to keep looking in case we find a later multiline // selection though. selectionType = "indentAtSelection"; } }); switch (selectionType) { case "indentAtBeginning": // Case 1 CodeMirror.commands.indentMore(instance); break; case "indentAtSelection": // Case 2 this._addIndentAtEachSelection(selections); break; case "indentAuto": // Case 3 this._autoIndentEachSelection(selections); break; } }; /** * @private * Handle left arrow, right arrow, backspace and delete keys when soft tabs are used. * @param {number} direction Direction of movement: 1 for forward, -1 for backward * @param {string} functionName name of the CodeMirror function to call if we handle the key */ Editor.prototype._handleSoftTabNavigation = function (direction, functionName) { var instance = this._codeMirror, overallJump = null; if (!instance.getOption("indentWithTabs") && PreferencesManager.get(SOFT_TABS)) { var indentUnit = instance.getOption("indentUnit"); _.each(this.getSelections(), function (sel) { if (CodeMirror.cmpPos(sel.start, sel.end) !== 0) { // This is a range - it will just collapse/be deleted regardless of the jump we set, so // we can just ignore it and continue. (We don't want to return false in this case since // we want to keep looking at other ranges.) return; } var cursor = sel.start, jump = (indentUnit === 0) ? 1 : cursor.ch % indentUnit, line = instance.getLine(cursor.line); // Don't do any soft tab handling if there are non-whitespace characters before the cursor in // any of the selections. if (line.substr(0, cursor.ch).search(/\S/) !== -1) { jump = null; } else if (direction === 1) { // right if (indentUnit) { jump = indentUnit - jump; } // Don't jump if it would take us past the end of the line, or if there are // non-whitespace characters within the jump distance. if (cursor.ch + jump > line.length || line.substr(cursor.ch, jump).search(/\S/) !== -1) { jump = null; } } else { // left // If we are on the tab boundary, jump by the full amount, // but not beyond the start of the line. if (jump === 0) { jump = indentUnit; } if (cursor.ch - jump < 0) { jump = null; } else { // We're moving left, so negate the jump. jump = -jump; } } // Did we calculate a jump, and is this jump value either the first one or // consistent with all the other jumps? If so, we're good. Otherwise, bail // out of the foreach, since as soon as we hit an inconsistent jump we don't // have to look any further. if (jump !== null && (overallJump === null || overallJump === jump)) { overallJump = jump; } else { overallJump = null; return false; } }); } if (overallJump === null) { // Just do the default move, which is one char in the given direction. overallJump = direction; } instance[functionName](overallJump, "char"); }; /** * Determine the mode to use from the document's language * Uses "text/plain" if the language does not define a mode * @return {string} The mode to use */ Editor.prototype._getModeFromDocument = function () { // We'd like undefined/null/"" to mean plain text mode. CodeMirror defaults to plaintext for any // unrecognized mode, but it complains on the console in that fallback case: so, convert // here so we're always explicit, avoiding console noise. return this.document.getLanguage().getMode() || "text/plain"; }; /** * Selects all text and maintains the current scroll position. */ Editor.prototype.selectAllNoScroll = function () { var cm = this._codeMirror, info = this._codeMirror.getScrollInfo(); // Note that we do not have to check for the visible range here. This // concern is handled internally by code mirror. cm.operation(function () { cm.scrollTo(info.left, info.top); cm.execCommand("selectAll"); }); }; /** * @return {boolean} True if editor is not showing the entire text of the document (i.e. an inline editor) */ Editor.prototype.isTextSubset = function () { return Boolean(this._visibleRange); }; /** * Ensures that the lines that are actually hidden in the inline editor correspond to * the desired visible range. */ Editor.prototype._updateHiddenLines = function () { if (this._visibleRange) { var cm = this._codeMirror, self = this; cm.operation(function () { self._hideMarks.forEach(function (mark) { if (mark) { mark.clear(); } }); self._hideMarks = []; self._hideMarks.push(self._hideLines(0, self._visibleRange.startLine)); self._hideMarks.push(self._hideLines(self._visibleRange.endLine + 1, self.lineCount())); }); } }; Editor.prototype._applyChanges = function (changeList) { // _visibleRange has already updated via its own Document listener. See if this change caused // it to lose sync. If so, our whole view is stale - signal our owner to close us. if (this._visibleRange) { if (this._visibleRange.startLine === null || this._visibleRange.endLine === null) { this.trigger("lostContent"); return; } } // Apply text changes to CodeMirror editor var cm = this._codeMirror; cm.operation(function () { var change, newText, i; for (i = 0; i < changeList.length; i++) { change = changeList[i]; newText = change.text.join('\n'); if (!change.from || !change.to) { if (change.from || change.to) { console.error("Change record received with only one end undefined--replacing entire text"); } cm.setValue(newText); } else { cm.replaceRange(newText, change.from, change.to, change.origin); } } }); // The update above may have inserted new lines - must hide any that fall outside our range this._updateHiddenLines(); }; /** * Responds to changes in the CodeMirror editor's text, syncing the changes to the Document. * There are several cases where we want to ignore a CodeMirror change: * - if we're the master editor, editor changes can be ignored because Document is already listening * for our changes * - if we're a secondary editor, editor changes should be ignored if they were caused by us reacting * to a Document change */ Editor.prototype._handleEditorChange = function (changeList) { // we're currently syncing from the Document, so don't echo back TO the Document if (this._duringSync) { return; } // Secondary editor: force creation of "master" editor backing the model, if doesn't exist yet this.document._ensureMasterEditor(); if (this.document._masterEditor !== this) { // Secondary editor: // we're not the ground truth; if we got here, this was a real editor change (not a // sync from the real ground truth), so we need to sync from us into the document // (which will directly push the change into the master editor). // FUTURE: Technically we should add a replaceRange() method to Document and go through // that instead of talking to its master editor directly. It's not clear yet exactly // what the right Document API would be, though. this._duringSync = true; this.document._masterEditor._applyChanges(changeList); this._duringSync = false; // Update which lines are hidden inside our editor, since we're not going to go through // _applyChanges() in our own editor. this._updateHiddenLines(); } // Else, Master editor: // we're the ground truth; nothing else to do, since Document listens directly to us // note: this change might have been a real edit made by the user, OR this might have // been a change synced from another editor // The "editorChange" event is mostly for the use of the CodeHintManager. // It differs from the normal "change" event, that it's actually publicly usable, // whereas the "change" event should be listened to on the document. Also the // Editor dispatches a change event before this event is dispatched, because // CodeHintManager needs to hook in here when other things are already done. this.trigger("editorChange", this, changeList); }; /** * Responds to changes in the Document's text, syncing the changes into our CodeMirror instance. * There are several cases where we want to ignore a Document change: * - if we're the master editor, Document changes should be ignored because we already have the right * text (either the change originated with us, or it has already been set into us by Document) * - if we're a secondary editor, Document changes should be ignored if they were caused by us sending * the document an editor change that originated with us */ Editor.prototype._handleDocumentChange = function (event, doc, changeList) { // we're currently syncing to the Document, so don't echo back FROM the Document if (this._duringSync) { return; } if (this.document._masterEditor !== this) { // Secondary editor: // we're not the ground truth; and if we got here, this was a Document change that // didn't come from us (e.g. a sync from another editor, a direct programmatic change // to the document, or a sync from external disk changes)... so sync from the Document this._duringSync = true; this._applyChanges(changeList); this._duringSync = false; } // Else, Master editor: // we're the ground truth; nothing to do since Document change is just echoing our // editor changes }; /** * Responds to the Document's underlying file being deleted. The Document is now basically dead, * so we must close. */ Editor.prototype._handleDocumentDeleted = function (event) { // Pass the delete event along as the cause (needed in MultiRangeInlineEditor) this.trigger("lostContent", event); }; /** * Responds to language changes, for instance when the file extension is changed. */ Editor.prototype._handleDocumentLanguageChanged = function (event) { this._codeMirror.setOption("mode", this._getModeFromDocument()); }; /** * Install event handlers on the CodeMirror instance, translating them into * jQuery events on the Editor instance. */ Editor.prototype._installEditorListeners = function () { var self = this; // Redispatch these CodeMirror key events as Editor events function _onKeyEvent(instance, event) { self.trigger("keyEvent", self, event); // deprecated self.trigger(event.type, self, event); return event.defaultPrevented; // false tells CodeMirror we didn't eat the event } this._codeMirror.on("keydown", _onKeyEvent); this._codeMirror.on("keypress", _onKeyEvent); this._codeMirror.on("keyup", _onKeyEvent); // FUTURE: if this list grows longer, consider making this a more generic mapping // NOTE: change is a "private" event--others shouldn't listen to it on Editor, only on // Document // Also, note that we use the new "changes" event in v4, which provides an array of // change objects. Our own event is still called just "change". this._codeMirror.on("changes", function (instance, changeList) { self.trigger("change", self, changeList); }); this._codeMirror.on("beforeChange", function (instance, changeObj) { self.trigger("beforeChange", self, changeObj); }); this._codeMirror.on("cursorActivity", function (instance) { self.trigger("cursorActivity", self); }); this._codeMirror.on("beforeSelectionChange", function (instance, selectionObj) { self.trigger("beforeSelectionChange", selectionObj); }); this._codeMirror.on("scroll", function (instance) { // If this editor is visible, close all dropdowns on scroll. // (We don't want to do this if we're just scrolling in a non-visible editor // in response to some document change event.) if (self.isFullyVisible()) { Menus.closeAll(); } self.trigger("scroll", self); }); // Convert CodeMirror onFocus events to EditorManager activeEditorChanged this._codeMirror.on("focus", function () { self._focused = true; self.trigger("focus", self); }); this._codeMirror.on("blur", function () { self._focused = false; self.trigger("blur", self); }); this._codeMirror.on("update", function (instance) { self.trigger("update", self); }); this._codeMirror.on("overwriteToggle", function (instance, newstate) { self.trigger("overwriteToggle", self, newstate); }); // Disable CodeMirror's drop handling if a file/folder is dropped this._codeMirror.on("drop", function (cm, event) { var files = event.dataTransfer.files; if (files && files.length) { event.preventDefault(); } }); // For word wrap. Code adapted from https://codemirror.net/demo/indentwrap.html# this._codeMirror.on("renderLine", function (cm, line, elt) { var charWidth = self._codeMirror.defaultCharWidth(); var off = CodeMirror.countColumn(line.text, null, cm.getOption("tabSize")) * charWidth; elt.style.textIndent = "-" + off + "px"; elt.style.paddingLeft = off + "px"; }); }; /** * Sets the contents of the editor, clears the undo/redo history and marks the document clean. Dispatches a change event. * Semi-private: only Document should call this. * @param {!string} text */ Editor.prototype._resetText = function (text) { var currentText = this._codeMirror.getValue(); // compare with ignoring line-endings, issue #11826 var textLF = text ? text.replace(/(\r\n|\r|\n)/g, "\n") : null; var currentTextLF = currentText ? currentText.replace(/(\r\n|\r|\n)/g, "\n") : null; if (textLF === currentTextLF) { // there's nothing to reset return; } var perfTimerName = PerfUtils.markStart("Editor._resetText()\t" + (!this.document || this.document.file.fullPath)); var cursorPos = this.getCursorPos(), scrollPos = this.getScrollPos(); // This *will* fire a change event, but we clear the undo immediately afterward this._codeMirror.setValue(text); this._codeMirror.refresh(); // Make sure we can't undo back to the empty state before setValue(), and mark // the document clean. this._codeMirror.clearHistory(); this._codeMirror.markClean(); // restore cursor and scroll positions this.setCursorPos(cursorPos); this.setScrollPos(scrollPos.x, scrollPos.y); PerfUtils.addMeasurement(perfTimerName); }; /** * Gets the file associated with this editor * This is a required Pane-View interface method * @return {!File} the file associated with this editor */ Editor.prototype.getFile = function () { return this.document.file; }; /** * Gets the current cursor position within the editor. * @param {boolean} expandTabs If true, return the actual visual column number instead of the character offset in * the "ch" property. * @param {?string} which Optional string indicating which end of the * selection to return. It may be "start", "end", "head" (the side of the * selection that moves when you press shift+arrow), or "anchor" (the * fixed side of the selection). Omitting the argument is the same as * passing "head". A {line, ch} object will be returned.) * @return {!{line:number, ch:number}} */ Editor.prototype.getCursorPos = function (expandTabs, which) { // Translate "start" and "end" to the official CM names (it actually // supports them as-is, but that isn't documented and we don't want to // rely on it). if (which === "start") { which = "from"; } else if (which === "end") { which = "to"; } var cursor = _copyPos(this._codeMirror.getCursor(which)); if (expandTabs) { cursor.ch = this.getColOffset(cursor); } return cursor; }; /** * Returns the display column (zero-based) for a given string-based pos. Differs from pos.ch only * when the line contains preceding \t chars. Result depends on the current tab size setting. * @param {!{line:number, ch:number}} pos * @return {number} */ Editor.prototype.getColOffset = function (pos) { var line = this._codeMirror.getRange({line: pos.line, ch: 0}, pos), tabSize = null, column = 0, i; for (i = 0; i < line.length; i++) { if (line[i] === '\t') { if (tabSize === null) { tabSize = Editor.getTabSize(); } if (tabSize > 0) { column += (tabSize - (column % tabSize)); } } else { column++; } } return column; }; /** * Returns the string-based pos for a given display column (zero-based) in given line. Differs from column * only when the line contains preceding \t chars. Result depends on the current tab size setting. * @param {number} lineNum Line number * @param {number} column Display column number * @return {number} */ Editor.prototype.getCharIndexForColumn = function (lineNum, column) { var line = this._codeMirror.getLine(lineNum), tabSize = null, iCol = 0, i; for (i = 0; iCol < column; i++) { if (line[i] === '\t') { if (tabSize === null) { tabSize = Editor.getTabSize(); } if (tabSize > 0) { iCol += (tabSize - (iCol % tabSize)); } } else { iCol++; } } return i; }; /** * Sets the cursor position within the editor. Removes any selection. * @param {number} line The 0 based line number. * @param {number} ch The 0 based character position; treated as 0 if unspecified. * @param {boolean=} center True if the view should be centered on the new cursor position. * @param {boolean=} expandTabs If true, use the actual visual column number instead of the character offset as * the "ch" parameter. */ Editor.prototype.setCursorPos = function (line, ch, center, expandTabs) { if (expandTabs) { ch = this.getColOffset({line: line, ch: ch}); } this._codeMirror.setCursor(line, ch); if (center) { this.centerOnCursor(); } }; /** * Set the editor size in pixels or percentage * @param {(number|string)} width * @param {(number|string)} height */ Editor.prototype.setSize = function (width, height) { this._codeMirror.setSize(width, height); }; /** @const */ var CENTERING_MARGIN = 0.15; /** * Scrolls the editor viewport to vertically center the line with the cursor, * but only if the cursor is currently near the edges of the viewport or * entirely outside the viewport. * * This does not alter the horizontal scroll position. * * @param {number} centerOptions Option value, or 0 for no options; one of the BOUNDARY_* constants above. */ Editor.prototype.centerOnCursor = function (centerOptions) { var $scrollerElement = $(this.getScrollerElement()); var editorHeight = $scrollerElement.height(); // we need to make adjustments for the statusbar's padding on the bottom and the menu bar on top. var statusBarHeight = $scrollerElement.outerHeight() - editorHeight; var menuBarHeight = $scrollerElement.offset().top; var documentCursorPosition = this._codeMirror.cursorCoords(null, "local").bottom; var screenCursorPosition = this._codeMirror.cursorCoords(null, "page").bottom - menuBarHeight; // If the cursor is already reasonably centered, we won't // make any change. "Reasonably centered" is defined as // not being within CENTERING_MARGIN of the top or bottom // of the editor (where CENTERING_MARGIN is a percentage // of the editor height). // For finding the first item (i.e. find while typing), do // not center if hit is in first half of screen because this // appears to be an unnecesary scroll. if ((_checkTopBoundary(centerOptions) && (screenCursorPosition < editorHeight * CENTERING_MARGIN)) || (_checkBottomBoundary(centerOptions) && (screenCursorPosition > editorHeight * (1 - CENTERING_MARGIN)))) { var pos = documentCursorPosition - editorHeight / 2 + statusBarHeight; var info = this._codeMirror.getScrollInfo(); pos = Math.min(Math.max(pos, 0), (info.height - info.clientHeight)); this.setScrollPos(null, pos); } }; /** * Given a position, returns its index within the text (assuming \n newlines) * @param {!{line:number, ch:number}} * @return {number} */ Editor.prototype.indexFromPos = function (coords) { return this._codeMirror.indexFromPos(coords); }; Editor.prototype.posFromIndex = function (index) { return this._codeMirror.posFromIndex(index); }; /** * Returns true if pos is between start and end (INclusive at start; EXclusive at end by default, * but overridable via the endInclusive flag). * @param {{line:number, ch:number}} pos * @param {{line:number, ch:number}} start * @param {{line:number, ch:number}} end * @param {boolean} endInclusive * */ Editor.prototype.posWithinRange = function (pos, start, end, endInclusive) { if (start.line <= pos.line && end.line >= pos.line) { if (endInclusive) { return (start.line < pos.line || start.ch <= pos.ch) && // inclusive (end.line > pos.line || end.ch >= pos.ch); // inclusive } else { return (start.line < pos.line || start.ch <= pos.ch) && // inclusive (end.line > pos.line || end.ch > pos.ch); // exclusive } } return false; }; /** * @return {boolean} True if there's a text selection; false if there's just an insertion point */ Editor.prototype.hasSelection = function () { return this._codeMirror.somethingSelected(); }; /** * @private * Takes an anchor/head pair and returns a start/end pair where the start is guaranteed to be <= end, and a "reversed" flag indicating * if the head is before the anchor. * @param {!{line: number, ch: number}} anchorPos * @param {!{line: number, ch: number}} headPos * @return {!{start:{line:number, ch:number}, end:{line:number, ch:number}}, reversed:boolean} the normalized range with start <= end */ function _normalizeRange(anchorPos, headPos) { if (headPos.line < anchorPos.line || (headPos.line === anchorPos.line && headPos.ch < anchorPos.ch)) { return {start: _copyPos(headPos), end: _copyPos(anchorPos), reversed: true}; } else { return {start: _copyPos(anchorPos), end: _copyPos(headPos), reversed: false}; } } /** * Gets the current selection; if there is more than one selection, returns the primary selection * (generally the last one made). Start is inclusive, end is exclusive. If there is no selection, * returns the current cursor position as both the start and end of the range (i.e. a selection * of length zero). If `reversed` is set, then the head of the selection (the end of the selection * that would be changed if the user extended the selection) is before the anchor. * @return {!{start:{line:number, ch:number}, end:{line:number, ch:number}}, reversed:boolean} */ Editor.prototype.getSelection = function () { return _normalizeRange(this.getCursorPos(false, "anchor"), this.getCursorPos(false, "head")); }; /** * Returns an array of current selections, nonoverlapping and sorted in document order. * Each selection is a start/end pair, with the start guaranteed to come before the end. * Cursors are represented as a range whose start is equal to the end. * If `reversed` is set, then the head of the selection * (the end of the selection that would be changed if the user extended the selection) * is before the anchor. * If `primary` is set, then that selection is the primary selection. * @return {Array.<{start:{line:number, ch:number}, end:{line:number, ch:number}, reversed:boolean, primary:boolean}>} */ Editor.prototype.getSelections = function () { var primarySel = this.getSelection(); return _.map(this._codeMirror.listSelections(), function (sel) { var result = _normalizeRange(sel.anchor, sel.head); if (result.start.line === primarySel.start.line && result.start.ch === primarySel.start.ch && result.end.line === primarySel.end.line && result.end.ch === primarySel.end.ch) { result.primary = true; } else { result.primary = false; } return result; }); }; /** * Takes the given selections, and expands each selection so it encompasses whole lines. Merges * adjacent line selections together. Keeps track of each original selection associated with a given * line selection (there might be multiple if individual selections were merged into a single line selection). * Useful for doing multiple-selection-aware line edits. * * @param {Array.<{start:{line:number, ch:number}, end:{line:number, ch:number}, reversed:boolean, primary:boolean}>} selections * The selections to expand. * @param {{expandEndAtStartOfLine: boolean, mergeAdjacent: boolean}} options * expandEndAtStartOfLine: true if a range selection that ends at the beginning of a line should be expanded * to encompass the line. Default false. * mergeAdjacent: true if adjacent line ranges should be merged. Default true. * @return {Array.<{selectionForEdit: {start:{line:number, ch:number}, end:{line:number, ch:number}, reversed:boolean, primary:boolean}, * selectionsToTrack: Array.<{start:{line:number, ch:number}, end:{line:number, ch:number}, reversed:boolean, primary:boolean}>}>} * The combined line selections. For each selection, `selectionForEdit` is the line selection, and `selectionsToTrack` is * the set of original selections that combined to make up the given line selection. Note that the selectionsToTrack will * include the original objects passed in `selections`, so if it is later mutated the original passed-in selections will be * mutated as well. */ Editor.prototype.convertToLineSelections = function (selections, options) { var self = this; options = options || {}; _.defaults(options, { expandEndAtStartOfLine: false, mergeAdjacent: true }); // Combine adjacent lines with selections so they don't collide with each other, as they would // if we did them individually. var combinedSelections = [], prevSel; _.each(selections, function (sel) { var newSel = _.cloneDeep(sel); // Adjust selection to encompass whole lines. newSel.start.ch = 0; // The end of the selection becomes the start of the next line, if it isn't already // or if expandEndAtStartOfLine is set. var hasSelection = (newSel.start.line !== newSel.end.line) || (newSel.start.ch !== newSel.end.ch); if (options.expandEndAtStartOfLine || !hasSelection || newSel.end.ch !== 0) { newSel.end = {line: newSel.end.line + 1, ch: 0}; } // If the start of the new selection is within the range of the previous (expanded) selection, merge // the two selections together, but keep track of all the original selections that were related to this // selection, so they can be properly adjusted. (We only have to check for the start being inside the previous // range - it can't be before it because the selections started out sorted.) if (prevSel && self.posWithinRange(newSel.start, prevSel.selectionForEdit.start, prevSel.selectionForEdit.end, options.mergeAdjacent)) { prevSel.selectionForEdit.end.line = newSel.end.line; prevSel.selectionsToTrack.push(sel); } else { prevSel = {selectionForEdit: newSel, selectionsToTrack: [sel]}; combinedSelections.push(prevSel); } }); return combinedSelections; }; /** * Returns the currently selected text, or "" if no selection. Includes \n if the * selection spans multiple lines (does NOT reflect the Document's line-endings style). By * default, returns only the contents of the primary selection, unless `allSelections` is true. * @param {boolean=} allSelections Whether to return the contents of all selections (separated * by newlines) instead of just the primary selection. Default false. * @return {!string} The selected text. */ Editor.prototype.getSelectedText = function (allSelections) { if (allSelections) { return this._codeMirror.getSelection(); } else { var sel = this.getSelection(); return this.document.getRange(sel.start, sel.end); } }; /** * Sets the current selection. Start is inclusive, end is exclusive. Places the cursor at the * end of the selection range. Optionally centers around the cursor after * making the selection * * @param {!{line:number, ch:number}} start * @param {{line:number, ch:number}=} end If not specified, defaults to start. * @param {boolean} center true to center the viewport * @param {number} centerOptions Option value, or 0 for no options; one of the BOUNDARY_* constants above. * @param {?string} origin An optional string that describes what other selection or edit operations this * should be merged with for the purposes of undo. See {@link Document#replaceRange} for more details. */ Editor.prototype.setSelection = function (start, end, center, centerOptions, origin) { this.setSelections([{start: start, end: end || start}], center, centerOptions, origin); }; /** * Sets a multiple selection, with the "primary" selection (the one returned by * getSelection() and getCursorPos()) defaulting to the last if not specified. * Overlapping ranges will be automatically merged, and the selection will be sorted. * Optionally centers around the primary selection after making the selection. * @param {!Array<{start:{line:number, ch:number}, end:{line:number, ch:number}, primary:boolean, reversed: boolean}>} selections * The selection ranges to set. If the start and end of a range are the same, treated as a cursor. * If reversed is true, set the anchor of the range to the end instead of the start. * If primary is true, this is the primary selection. Behavior is undefined if more than * one selection has primary set to true. If none has primary set to true, the last one is primary. * @param {boolean} center true to center the viewport around the primary selection. * @param {number} centerOptions Option value, or 0 for no options; one of the BOUNDARY_* constants above. * @param {?string} origin An optional string that describes what other selection or edit operations this * should be merged with for the purposes of undo. See {@link Document#replaceRange} for more details. */ Editor.prototype.setSelections = function (selections, center, centerOptions, origin) { var primIndex = selections.length - 1, options; if (origin) { options = { origin: origin }; } this._codeMirror.setSelections(_.map(selections, function (sel, index) { if (sel.primary) { primIndex = index; } return { anchor: sel.reversed ? sel.end : sel.start, head: sel.reversed ? sel.start : sel.end }; }), primIndex, options); if (center) { this.centerOnCursor(centerOptions); } }; /** * Sets the editors overwrite mode state. If null is passed, the state is toggled. * * @param {?boolean} start */ Editor.prototype.toggleOverwrite = function (state) { this._codeMirror.toggleOverwrite(state); }; /** * Selects word that the given pos lies within or adjacent to. If pos isn't touching a word * (e.g. within a token like "//"), moves the cursor to pos without selecting a range. * @param {!{line:number, ch:number}} */ Editor.prototype.selectWordAt = function (pos) { var word = this._codeMirror.findWordAt(pos); this.setSelection(word.anchor, word.head); }; /** * Gets the total number of lines in the document (includes lines not visible in the viewport) * @return {!number} */ Editor.prototype.lineCount = function () { return this._codeMirror.lineCount(); }; /** * Deterines if line is fully visible. * @param {number} zero-based index of the line to test * @return {boolean} true if the line is fully visible, false otherwise */ Editor.prototype.isLineVisible = function (line) { var coords = this._codeMirror.charCoords({line: line, ch: 0}, "local"), scrollInfo = this._codeMirror.getScrollInfo(), top = scrollInfo.top, bottom = scrollInfo.top + scrollInfo.clientHeight; // Check top and bottom and return false for partially visible lines. return (coords.top >= top && coords.bottom <= bottom); }; /** * Gets the number of the first visible line in the editor. * @return {number} The 0-based index of the first visible line. */ Editor.prototype.getFirstVisibleLine = function () { return (this._visibleRange ? this._visibleRange.startLine : 0); }; /** * Gets the number of the last visible line in the editor. * @return {number} The 0-based index of the last visible line. */ Editor.prototype.getLastVisibleLine = function () { return (this._visibleRange ? this._visibleRange.endLine : this.lineCount() - 1); }; /* Hides the specified line number in the editor * @param {!from} line to start hiding from (inclusive) * @param {!to} line to end hiding at (exclusive) * @return {TextMarker} The CodeMirror mark object that's hiding the lines */ Editor.prototype._hideLines = function (from, to) { if (to <= from) { return; } // We set clearWhenEmpty: false so that if there's a blank line at the beginning or end of // the document, and that's the only hidden line, we can still actually hide it. Doing so // requires us to create a 0-length marked span, which would ordinarily be cleaned up by CM // if clearWithEmpty is true. See https://groups.google.com/forum/#!topic/codemirror/RB8VNF8ow2w var value = this._codeMirror.markText( {line: from, ch: 0}, {line: to - 1, ch: this._codeMirror.getLine(to - 1).length}, {collapsed: true, inclusiveLeft: true, inclusiveRight: true, clearWhenEmpty: false} ); return value; }; /** * Gets the total height of the document in pixels (not the viewport) * @return {!number} height in pixels */ Editor.prototype.totalHeight = function () { return this.getScrollerElement().scrollHeight; }; /** * Gets the scroller element from the editor. * @return {!HTMLDivElement} scroller */ Editor.prototype.getScrollerElement = function () { return this._codeMirror.getScrollerElement(); }; /** * Gets the root DOM node of the editor. * @return {!HTMLDivElement} The editor's root DOM node. */ Editor.prototype.getRootElement = function () { return this._codeMirror.getWrapperElement(); }; /** * Gets the lineSpace element within the editor (the container around the individual lines of code). * FUTURE: This is fairly CodeMirror-specific. Logic that depends on this may break if we switch * editors. * @return {!HTMLDivElement} The editor's lineSpace element. */ Editor.prototype._getLineSpaceElement = function () { return $(".CodeMirror-lines", this.getScrollerElement()).children().get(0); }; /** * Returns the current scroll position of the editor. * @return {{x:number, y:number}} The x,y scroll position in pixels */ Editor.prototype.getScrollPos = function () { var scrollInfo = this._codeMirror.getScrollInfo(); return { x: scrollInfo.left, y: scrollInfo.top }; }; /** * Restores and adjusts the current scroll position of the editor. * @param {{x:number, y:number}} scrollPos - The x,y scroll position in pixels * @param {!number} heightDelta - The amount of delta H to apply to the scroll position */ Editor.prototype.adjustScrollPos = function (scrollPos, heightDelta) { this._codeMirror.scrollTo(scrollPos.x, scrollPos.y + heightDelta); }; /** * Sets the current scroll position of the editor. * @param {number} x scrollLeft position in pixels * @param {number} y scrollTop position in pixels */ Editor.prototype.setScrollPos = function (x, y) { this._codeMirror.scrollTo(x, y); }; /* * Returns the current text height of the editor. * @return {number} Height of the text in pixels */ Editor.prototype.getTextHeight = function () { return this._codeMirror.defaultTextHeight(); }; /** * Adds an inline widget below the given line. If any inline widget was already open for that * line, it is closed without warning. * @param {!{line:number, ch:number}} pos Position in text to anchor the inline. * @param {!InlineWidget} inlineWidget The widget to add. * @param {boolean=} scrollLineIntoView Scrolls the associated line into view. Default true. * @return {$.Promise} A promise object that is resolved when the widget has been added (but might * still be animating open). Never rejected. */ Editor.prototype.addInlineWidget = function (pos, inlineWidget, scrollLineIntoView) { var self = this, queue = this._inlineWidgetQueues[pos.line], deferred = new $.Deferred(); if (!queue) { queue = new Async.PromiseQueue(); this._inlineWidgetQueues[pos.line] = queue; } queue.add(function () { self._addInlineWidgetInternal(pos, inlineWidget, scrollLineIntoView, deferred); return deferred.promise(); }); return deferred.promise(); }; /** * @private * Does the actual work of addInlineWidget(). */ Editor.prototype._addInlineWidgetInternal = function (pos, inlineWidget, scrollLineIntoView, deferred) { var self = this; this.removeAllInlineWidgetsForLine(pos.line).done(function () { if (scrollLineIntoView === undefined) { scrollLineIntoView = true; } if (scrollLineIntoView) { self._codeMirror.scrollIntoView(pos); } inlineWidget.info = self._codeMirror.addLineWidget(pos.line, inlineWidget.htmlContent, { coverGutter: true, noHScroll: true }); CodeMirror.on(inlineWidget.info.line, "delete", function () { self._removeInlineWidgetInternal(inlineWidget); }); self._inlineWidgets.push(inlineWidget); // Set up the widget to start closed, then animate open when its initial height is set. inlineWidget.$htmlContent.height(0); AnimationUtils.animateUsingClass(inlineWidget.htmlContent, "animating") .done(function () { deferred.resolve(); }); // Callback to widget once parented to the editor. The widget should call back to // setInlineWidgetHeight() in order to set its initial height and animate open. inlineWidget.onAdded(); }); }; /** * Removes all inline widgets */ Editor.prototype.removeAllInlineWidgets = function () { // copy the array because _removeInlineWidgetInternal will modify the original var widgets = [].concat(this.getInlineWidgets()); return Async.doInParallel( widgets, this.removeInlineWidget.bind(this) ); }; /** * Removes the given inline widget. * @param {number} inlineWidget The widget to remove. * @return {$.Promise} A promise that is resolved when the inline widget is fully closed and removed from the DOM. */ Editor.prototype.removeInlineWidget = function (inlineWidget) { var deferred = new $.Deferred(), self = this; function finishRemoving() { self._codeMirror.removeLineWidget(inlineWidget.info); self._removeInlineWidgetInternal(inlineWidget); deferred.resolve(); } if (!inlineWidget.closePromise) { // Remove the inline widget from our internal list immediately, so // everyone external to us knows it's essentially already gone. We // don't want to wait until it's done animating closed (but we do want // the other stuff in _removeInlineWidgetInternal to wait until then). self._removeInlineWidgetFromList(inlineWidget); // If we're not visible (in which case the widget will have 0 client height), // don't try to do the animation, because nothing will happen and we won't get // called back right away. (The animation would happen later when we switch // back to the editor.) if (self.isFullyVisible()) { AnimationUtils.animateUsingClass(inlineWidget.htmlContent, "animating") .done(finishRemoving); inlineWidget.$htmlContent.height(0); } else { finishRemoving(); } inlineWidget.closePromise = deferred.promise(); } return inlineWidget.closePromise; }; /** * Removes all inline widgets for a given line * @param {number} lineNum The line number to modify */ Editor.prototype.removeAllInlineWidgetsForLine = function (lineNum) { var lineInfo = this._codeMirror.lineInfo(lineNum), widgetInfos = (lineInfo && lineInfo.widgets) ? [].concat(lineInfo.widgets) : null, self = this; if (widgetInfos && widgetInfos.length) { // Map from CodeMirror LineWidget to Brackets InlineWidget var inlineWidget, allWidgetInfos = this._inlineWidgets.map(function (w) { return w.info; }); return Async.doInParallel( widgetInfos, function (info) { // Lookup the InlineWidget object using the same index inlineWidget = self._inlineWidgets[allWidgetInfos.indexOf(info)]; if (inlineWidget) { return self.removeInlineWidget(inlineWidget); } else { return new $.Deferred().resolve().promise(); } } ); } else { return new $.Deferred().resolve().promise(); } }; /** * Cleans up the given inline widget from our internal list of widgets. It's okay * to call this multiple times for the same widget--it will just do nothing if * the widget has already been removed. * @param {InlineWidget} inlineWidget an inline widget. */ Editor.prototype._removeInlineWidgetFromList = function (inlineWidget) { var l = this._inlineWidgets.length, i; for (i = 0; i < l; i++) { if (this._inlineWidgets[i] === inlineWidget) { this._inlineWidgets.splice(i, 1); break; } } }; /** * Removes the inline widget from the editor and notifies it to clean itself up. * @param {InlineWidget} inlineWidget an inline widget. */ Editor.prototype._removeInlineWidgetInternal = function (inlineWidget) { if (!inlineWidget.isClosed) { this._removeInlineWidgetFromList(inlineWidget); inlineWidget.onClosed(); inlineWidget.isClosed = true; } }; /** * Returns a list of all inline widgets currently open in this editor. Each entry contains the * inline's id, and the data parameter that was passed to addInlineWidget(). * @return {!Array.<{id:number, data:Object}>} */ Editor.prototype.getInlineWidgets = function () { return this._inlineWidgets; }; /** * Returns the currently focused inline widget, if any. * @return {?InlineWidget} */ Editor.prototype.getFocusedInlineWidget = function () { var result = null; this.getInlineWidgets().forEach(function (widget) { if (widget.hasFocus()) { result = widget; } }); return result; }; /** * Display temporary popover message at current cursor position. Display message above * cursor if space allows, otherwise below. * * @param {string} errorMsg Error message to display */ Editor.prototype.displayErrorMessageAtCursor = function (errorMsg) { var arrowBelow, cursorPos, cursorCoord, popoverRect, top, left, clip, arrowCenter, arrowLeft, self = this, POPOVER_MARGIN = 10, POPOVER_ARROW_HALF_WIDTH = 10, POPOVER_ARROW_HALF_BASE = POPOVER_ARROW_HALF_WIDTH + 3; // 3 is border radius function _removeListeners() { self.off(".msgbox"); } // PopUpManager.removePopUp() callback function _clearMessagePopover() { if (self._$messagePopover && self._$messagePopover.length > 0) { // self._$messagePopover.remove() is done by PopUpManager self._$messagePopover = null; } _removeListeners(); } // PopUpManager.removePopUp() is called either directly by this closure, or by // PopUpManager as a result of another popup being invoked. function _removeMessagePopover() { if (self._$messagePopover) { PopUpManager.removePopUp(self._$messagePopover); } } function _addListeners() { self .on("blur.msgbox", _removeMessagePopover) .on("change.msgbox", _removeMessagePopover) .on("cursorActivity.msgbox", _removeMessagePopover) .on("update.msgbox", _removeMessagePopover); } // Only 1 message at a time if (this._$messagePopover) { _removeMessagePopover(); } // Make sure cursor is in view cursorPos = this.getCursorPos(); this._codeMirror.scrollIntoView(cursorPos); // Determine if arrow is above or below cursorCoord = this._codeMirror.charCoords(cursorPos); // Assume popover height is max of 2 lines arrowBelow = (cursorCoord.top > 100); // Text is dynamic, so build popover first so we can measure final width this._$messagePopover = $("
    ").addClass("popover-message").appendTo($("body")); if (!arrowBelow) { $("
    ").addClass("arrowAbove").appendTo(this._$messagePopover); } $("
    ").addClass("text").appendTo(this._$messagePopover).html(errorMsg); if (arrowBelow) { $("
    ").addClass("arrowBelow").appendTo(this._$messagePopover); } // Estimate where to position popover. top = (arrowBelow) ? cursorCoord.top - this._$messagePopover.height() - POPOVER_MARGIN : cursorCoord.bottom + POPOVER_MARGIN; left = cursorCoord.left - (this._$messagePopover.width() / 2); popoverRect = { top: top, left: left, height: this._$messagePopover.height(), width: this._$messagePopover.width() }; // See if popover is clipped on any side clip = ViewUtils.getElementClipSize($("#editor-holder"), popoverRect); // Prevent horizontal clipping if (clip.left > 0) { left += clip.left; } else if (clip.right > 0) { left -= clip.right; } // Popover text and arrow are positioned individually this._$messagePopover.css({"top": top, "left": left}); // Position popover arrow centered over/under cursor... arrowCenter = cursorCoord.left - left; // ... but don't let it slide off text box arrowCenter = Math.min(popoverRect.width - POPOVER_ARROW_HALF_BASE, Math.max(arrowCenter, POPOVER_ARROW_HALF_BASE)); arrowLeft = arrowCenter - POPOVER_ARROW_HALF_WIDTH; if (arrowBelow) { this._$messagePopover.find(".arrowBelow").css({"margin-left": arrowLeft}); } else { this._$messagePopover.find(".arrowAbove").css({"margin-left": arrowLeft}); } // Add listeners PopUpManager.addPopUp(this._$messagePopover, _clearMessagePopover, true); _addListeners(); // Animate open AnimationUtils.animateUsingClass(this._$messagePopover[0], "animateOpen").done(function () { // Make sure we still have a popover if (self._$messagePopover && self._$messagePopover.length > 0) { self._$messagePopover.addClass("open"); // Don't add scroll listeners until open so we don't get event // from scrolling cursor into view self.on("scroll.msgbox", _removeMessagePopover); // Animate closed -- which includes delay to show message AnimationUtils.animateUsingClass(self._$messagePopover[0], "animateClose", 6000) .done(_removeMessagePopover); } }); }; /** * Returns the offset of the top of the virtual scroll area relative to the browser window (not the editor * itself). Mainly useful for calculations related to scrollIntoView(), where you're starting with the * offset() of a child widget (relative to the browser window) and need to figure out how far down it is from * the top of the virtual scroll area (excluding the top padding). * @return {number} */ Editor.prototype.getVirtualScrollAreaTop = function () { var topPadding = this._getLineSpaceElement().offsetTop, // padding within mover scroller = this.getScrollerElement(); return $(scroller).offset().top - scroller.scrollTop + topPadding; }; /** * Sets the height of an inline widget in this editor. * @param {!InlineWidget} inlineWidget The widget whose height should be set. * @param {!number} height The height of the widget. * @param {boolean=} ensureVisible Whether to scroll the entire widget into view. Default false. */ Editor.prototype.setInlineWidgetHeight = function (inlineWidget, height, ensureVisible) { var self = this, node = inlineWidget.htmlContent, oldHeight = (node && $(node).height()) || 0, changed = (oldHeight !== height), isAttached = inlineWidget.info !== undefined; function updateHeight() { // Notify CodeMirror for the height change. if (isAttached) { inlineWidget.info.changed(); } } function setOuterHeight() { function finishAnimating(e) { if (e.target === node) { updateHeight(); $(node).off("webkitTransitionEnd", finishAnimating); } } $(node).height(height); if ($(node).hasClass("animating")) { $(node).on("webkitTransitionEnd", finishAnimating); } else { updateHeight(); } } // Make sure we set an explicit height on the widget, so children can use things like // min-height if they want. if (changed || !node.style.height) { // If we're animating, set the wrapper's height on a timeout so the layout is finished before we animate. if ($(node).hasClass("animating")) { window.setTimeout(setOuterHeight, 0); } else { setOuterHeight(); } } if (ensureVisible && isAttached) { var offset = $(node).offset(), // offset relative to document position = $(node).position(), // position within parent linespace scrollerTop = self.getVirtualScrollAreaTop(); self._codeMirror.scrollIntoView({ left: position.left, top: offset.top - scrollerTop, right: position.left, // don't try to make the right edge visible bottom: offset.top + height - scrollerTop }); } }; /** * @private * Get the starting line number for an inline widget. * @param {!InlineWidget} inlineWidget * @return {number} The line number of the widget or -1 if not found. */ Editor.prototype._getInlineWidgetLineNumber = function (inlineWidget) { return this._codeMirror.getLineNumber(inlineWidget.info.line); }; /** Gives focus to the editor control */ Editor.prototype.focus = function () { // Focusing an editor synchronously triggers focus/blur handlers. If a blur handler attemps to focus // another editor, we'll put CM in a bad state (because CM assumes programmatically focusing itself // will always succeed, and if you're in the middle of another focus change that appears to be untrue). // So instead, we simply ignore reentrant focus attempts. // See bug #2951 for an example of this happening and badly hosing things. if (_duringFocus) { return; } _duringFocus = true; try { this._codeMirror.focus(); } finally { _duringFocus = false; } }; /** Returns true if the editor has focus */ Editor.prototype.hasFocus = function () { return this._focused; }; /* * @typedef {scrollPos:{x:number, y:number},Array.<{start:{line:number, ch:number},end:{line:number, ch:number}}>} EditorViewState */ /* * returns the view state for the editor * @return {!EditorViewState} */ Editor.prototype.getViewState = function () { return { selections: this.getSelections(), scrollPos: this.getScrollPos() }; }; /* * Restores the view state * @param {!EditorViewState} viewState - the view state object to restore */ Editor.prototype.restoreViewState = function (viewState) { if (viewState.selection) { // We no longer write out single-selection, but there might be some view state // from an older version. this.setSelection(viewState.selection.start, viewState.selection.end); } if (viewState.selections) { this.setSelections(viewState.selections); } if (viewState.scrollPos) { this.setScrollPos(viewState.scrollPos.x, viewState.scrollPos.y); } }; /** * Re-renders the editor UI * @param {boolean=} handleResize true if this is in response to resizing the editor. Default false. */ Editor.prototype.refresh = function (handleResize) { // If focus is currently in a child of the CodeMirror editor (e.g. in an inline widget), but not in // the CodeMirror input field itself, remember the focused item so we can restore focus after the // refresh (which might cause the widget to be removed from the display list temporarily). var focusedItem = window.document.activeElement, restoreFocus = $.contains(this._codeMirror.getScrollerElement(), focusedItem); this._codeMirror.refresh(); if (restoreFocus) { focusedItem.focus(); } }; /** * Re-renders the editor, and all children inline editors. * @param {boolean=} handleResize true if this is in response to resizing the editor. Default false. */ Editor.prototype.refreshAll = function (handleResize) { this.refresh(handleResize); this.getInlineWidgets().forEach(function (inlineWidget) { inlineWidget.refresh(); }); }; /** Undo the last edit. */ Editor.prototype.undo = function () { this._codeMirror.undo(); }; /** Redo the last un-done edit. */ Editor.prototype.redo = function () { this._codeMirror.redo(); }; /** * View API Visibility Change Notification handler. This is also * called by the native "setVisible" API which refresh can be optimized * @param {boolean} show true to show the editor, false to hide it * @param {boolean} refresh true (default) to refresh the editor, false to skip refreshing it */ Editor.prototype.notifyVisibilityChange = function (show, refresh) { if (show && (refresh || refresh === undefined)) { this.refresh(); } if (show) { this._inlineWidgets.forEach(function (inlineWidget) { inlineWidget.onParentShown(); }); } }; /** * Shows or hides the editor within its parent. Does not force its ancestors to * become visible. * @param {boolean} show true to show the editor, false to hide it * @param {boolean} refresh true (default) to refresh the editor, false to skip refreshing it */ Editor.prototype.setVisible = function (show, refresh) { this.$el.css("display", (show ? "" : "none")); this.notifyVisibilityChange(show, refresh); }; /** * Returns true if the editor is fully visible--i.e., is in the DOM, all ancestors are * visible, and has a non-zero width/height. */ Editor.prototype.isFullyVisible = function () { return $(this.getRootElement()).is(":visible"); }; /** * Gets the syntax-highlighting mode for the given range. * Returns null if the mode at the start of the selection differs from the mode at the end - * an *approximation* of whether the mode is consistent across the whole range (a pattern like * A-B-A would return A as the mode, not null). * * @param {!{line: number, ch: number}} start The start of the range to check. * @param {!{line: number, ch: number}} end The end of the range to check. * @param {boolean=} knownMixed Whether we already know we're in a mixed mode and need to check both * the start and end. * @return {?(Object|string)} Name of syntax-highlighting mode, or object containing a "name" property * naming the mode along with configuration options required by the mode. * @see {@link LanguageManager::#getLanguageForPath} and {@link LanguageManager::Language#getMode}. */ Editor.prototype.getModeForRange = function (start, end, knownMixed) { var outerMode = this._codeMirror.getMode(), startMode = TokenUtils.getModeAt(this._codeMirror, start), endMode = TokenUtils.getModeAt(this._codeMirror, end); if (!knownMixed && outerMode.name === startMode.name) { // Mode does not vary: just use the editor-wide mode name return this._codeMirror.getOption("mode"); } else if (!startMode || !endMode || startMode.name !== endMode.name) { return null; } else { return startMode; } }; /** * Gets the syntax-highlighting mode for the current selection or cursor position. (The mode may * vary within one file due to embedded languages, e.g. JS embedded in an HTML script block). See * `getModeForRange()` for how this is determined for a single selection. * * If there are multiple selections, this will return a mode only if all the selections are individually * consistent and resolve to the same mode. * * @return {?(Object|string)} Name of syntax-highlighting mode, or object containing a "name" property * naming the mode along with configuration options required by the mode. * @see {@link LanguageManager::#getLanguageForPath} and {@link LanguageManager::Language#getMode}. */ Editor.prototype.getModeForSelection = function () { // Check for mixed mode info var self = this, sels = this.getSelections(), primarySel = this.getSelection(), outerMode = this._codeMirror.getMode(), startMode = TokenUtils.getModeAt(this._codeMirror, primarySel.start), isMixed = (outerMode.name !== startMode.name); if (isMixed) { // This is the magic code to let the code view know that we are in 'css' context // if the CodeMirror outermode is 'htmlmixed' and we are in 'style' attributes // value context. This has to be done as CodeMirror doesn't yet think this as 'css' // This magic is executed only when user is having a cursor and not selection // We will enable selection handling one we figure a way out to handle mixed scope selection if (outerMode.name === 'htmlmixed' && primarySel.start.line === primarySel.end.line && primarySel.start.ch === primarySel.end.ch) { var tagInfo = HTMLUtils.getTagInfo(this, primarySel.start, true), tokenType = tagInfo.position.tokenType; if (tokenType === HTMLUtils.ATTR_VALUE && tagInfo.attr.name.toLowerCase() === 'style') { return 'css'; } } // Shortcut the first check to avoid getModeAt(), which can be expensive if (primarySel.start.line !== primarySel.end.line || primarySel.start.ch !== primarySel.end.ch) { var endMode = TokenUtils.getModeAt(this._codeMirror, primarySel.end); if (startMode.name !== endMode.name) { return null; } } // If mixed mode, check that mode is the same at start & end of each selection var hasMixedSel = _.some(sels, function (sel) { if (sels === primarySel) { // We already checked this before, so we know it's not mixed. return false; } var rangeMode = self.getModeForRange(sel.start, sel.end, true); return (!rangeMode || rangeMode.name !== startMode.name); }); if (hasMixedSel) { return null; } return startMode.name; } else { // Mode does not vary: just use the editor-wide mode return this._codeMirror.getOption("mode"); } }; /* * gets the language for the selection. (Javascript selected from an HTML document or CSS selected from an HTML document, etc...) * @return {!Language} */ Editor.prototype.getLanguageForSelection = function () { return this.document.getLanguage().getLanguageForMode(this.getModeForSelection()); }; /** * Gets the syntax-highlighting mode for the document. * * @return {Object|String} Object or Name of syntax-highlighting mode * @see {@link LanguageManager::#getLanguageForPath|LanguageManager.getLanguageForPath} and {@link LanguageManager::Language#getMode|Language.getMode}. */ Editor.prototype.getModeForDocument = function () { return this._codeMirror.getOption("mode"); }; /** * The Document we're bound to * @type {!Document} */ Editor.prototype.document = null; /** * The Editor's last known width. * Used in conjunction with updateLayout to recompute the layout * if the parent container changes its size since our last layout update. * @type {?number} */ Editor.prototype._lastEditorWidth = null; /** * If true, we're in the middle of syncing to/from the Document. Used to ignore spurious change * events caused by us (vs. change events caused by others, which we need to pay attention to). * @type {!boolean} */ Editor.prototype._duringSync = false; /** * @private * NOTE: this is actually "semi-private": EditorManager also accesses this field... as well as * a few other modules. However, we should try to gradually move most code away from talking to * CodeMirror directly. * @type {!CodeMirror} */ Editor.prototype._codeMirror = null; /** * @private * @type {!Array.<{id:number, data:Object}>} */ Editor.prototype._inlineWidgets = null; /** * @private * @type {?TextRange} */ Editor.prototype._visibleRange = null; /** * @private * @type {Object} * Promise queues for inline widgets being added to a given line. */ Editor.prototype._inlineWidgetQueues = null; /** * @private * @type {Array} * A list of objects corresponding to the markers that are hiding lines in the current editor. */ Editor.prototype._hideMarks = null; /** * @private * * Retrieve the value of the named preference for this document. * * @param {string} prefName Name of preference to retrieve. * @return {*} current value of that pref */ Editor.prototype._getOption = function (prefName) { return PreferencesManager.get(prefName, PreferencesManager._buildContext(this.document.file.fullPath, this.document.getLanguage().getId())); }; /** * @private * * Updates the editor to the current value of prefName for the file being edited. * * @param {string} prefName Name of the preference to visibly update */ Editor.prototype._updateOption = function (prefName) { var oldValue = this._currentOptions[prefName], newValue = this._getOption(prefName); if (oldValue !== newValue) { this._currentOptions[prefName] = newValue; if (prefName === USE_TAB_CHAR) { this._codeMirror.setOption(cmOptions[prefName], newValue); this._codeMirror.setOption("indentUnit", newValue === true ? this._currentOptions[TAB_SIZE] : this._currentOptions[SPACE_UNITS] ); } else if (prefName === STYLE_ACTIVE_LINE) { this._updateStyleActiveLine(); } else if (prefName === SCROLL_PAST_END && this._visibleRange) { // Do not apply this option to inline editors return; } else if (prefName === SHOW_LINE_NUMBERS) { Editor._toggleLinePadding(!newValue); this._codeMirror.setOption(cmOptions[SHOW_LINE_NUMBERS], newValue); if (newValue) { Editor.registerGutter(LINE_NUMBER_GUTTER, LINE_NUMBER_GUTTER_PRIORITY); } else { Editor.unregisterGutter(LINE_NUMBER_GUTTER); } this.refreshAll(); } else { this._codeMirror.setOption(cmOptions[prefName], newValue); } this.trigger("optionChange", prefName, newValue); } }; /** * @private * * Used to ensure that "style active line" is turned off when there is a selection. */ Editor.prototype._updateStyleActiveLine = function () { if (this.hasSelection()) { if (this._codeMirror.getOption("styleActiveLine")) { this._codeMirror.setOption("styleActiveLine", false); } } else { this._codeMirror.setOption("styleActiveLine", this._currentOptions[STYLE_ACTIVE_LINE]); } }; /** * resizes the editor to fill its parent container * should not be used on inline editors * @param {boolean=} forceRefresh - forces the editor to update its layout * even if it already matches the container's height / width */ Editor.prototype.updateLayout = function (forceRefresh) { var curRoot = this.getRootElement(), curWidth = $(curRoot).width(), $editorHolder = this.$el.parent(), editorAreaHt = $editorHolder.height(); if (!curRoot.style.height || $(curRoot).height() !== editorAreaHt) { // Call setSize() instead of $.height() to allow CodeMirror to // check for options like line wrapping this.setSize(null, editorAreaHt); if (forceRefresh === undefined) { forceRefresh = true; } } else if (curWidth !== this._lastEditorWidth) { if (forceRefresh === undefined) { forceRefresh = true; } } this._lastEditorWidth = curWidth; if (forceRefresh) { this.refreshAll(forceRefresh); } }; /** * Clears all marks from the gutter with the specified name. * @param {string} name The name of the gutter to clear. */ Editor.prototype.clearGutter = function (name) { this._codeMirror.clearGutter(name); }; /** * Renders all registered gutters * @private */ Editor.prototype._renderGutters = function () { var languageId = this.document.getLanguage().getId(); function _filterByLanguages(gutter) { return !gutter.languages || gutter.languages.indexOf(languageId) > -1; } function _sortByPriority(a, b) { return a.priority - b.priority; } function _getName(gutter) { return gutter.name; } var gutters = registeredGutters.map(_getName), rootElement = this.getRootElement(); // If the line numbers gutter has not been explicitly registered and the CodeMirror lineNumbes option is // set to true, we explicitly add the line numbers gutter. This case occurs the first time the editor loads // and showLineNumbers is set to true in preferences if (gutters.indexOf(LINE_NUMBER_GUTTER) < 0 && this._codeMirror.getOption(cmOptions[SHOW_LINE_NUMBERS])) { registeredGutters.push({name: LINE_NUMBER_GUTTER, priority: LINE_NUMBER_GUTTER_PRIORITY}); } gutters = registeredGutters.sort(_sortByPriority) .filter(_filterByLanguages) .map(_getName); this._codeMirror.setOption("gutters", gutters); this._codeMirror.refresh(); if (gutters.indexOf(LINE_NUMBER_GUTTER) < 0) { $(rootElement).addClass("linenumber-disabled"); } else { $(rootElement).removeClass("linenumber-disabled"); } }; /** * Sets the marker for the specified gutter on the specified line number * @param {string} lineNumber The line number for the inserted gutter marker * @param {string} gutterName The name of the gutter * @param {object} marker The dom element representing the marker to the inserted in the gutter */ Editor.prototype.setGutterMarker = function (lineNumber, gutterName, marker) { var gutterNameRegistered = registeredGutters.some(function (gutter) { return gutter.name === gutterName; }); if (!gutterNameRegistered) { console.warn("Gutter name must be registered before calling editor.setGutterMarker"); return; } this._codeMirror.setGutterMarker(lineNumber, gutterName, marker); }; /** * Returns the list of gutters current registered on all editors. * @return {!Array.<{name: string, priority: number}>} */ Editor.getRegisteredGutters = function () { return registeredGutters; }; /** * Registers the gutter with the specified name at the given priority. * @param {string} name The name of the gutter. * @param {number} priority A number denoting the priority of the gutter. Priorities higher than LINE_NUMBER_GUTTER_PRIORITY appear after the line numbers. Priority less than LINE_NUMBER_GUTTER_PRIORITY appear before. * @param {?Array} languageIds A list of language ids that this gutter is valid for. If no language ids are passed, then the gutter is valid in all languages. */ Editor.registerGutter = function (name, priority, languageIds) { if (isNaN(priority)) { console.warn("A non-numeric priority value was passed to registerGutter. The value will default to 0."); priority = 0; } if (!name || typeof name !== "string") { console.error("The name of the registered gutter must be a string."); return; } var gutter = {name: name, priority: priority, languages: languageIds}, gutterExists = registeredGutters.some(function (gutter) { return gutter.name === name; }); if (!gutterExists) { registeredGutters.push(gutter); } Editor.forEveryEditor(function (editor) { editor._renderGutters(); }); }; /** * Unregisters the gutter with the specified name and removes it from the UI. * @param {string} name The name of the gutter to be unregistered. */ Editor.unregisterGutter = function (name) { var i, gutter; registeredGutters = registeredGutters.filter(function (gutter) { return gutter.name !== name; }); Editor.forEveryEditor(function (editor) { editor._renderGutters(); }); }; // Global settings that affect Editor instances that share the same preference locations /** * Sets whether to use tab characters (vs. spaces) when inserting new text. * Affects any editors that share the same preference location. * @param {boolean} value * @param {string=} fullPath Path to file to get preference for * @return {boolean} true if value was valid */ Editor.setUseTabChar = function (value, fullPath) { var options = fullPath && {context: fullPath}; return PreferencesManager.set(USE_TAB_CHAR, value, options); }; /** * Gets whether the specified or current file uses tab characters (vs. spaces) when inserting new text * @param {string=} fullPath Path to file to get preference for * @return {boolean} */ Editor.getUseTabChar = function (fullPath) { return PreferencesManager.get(USE_TAB_CHAR, _buildPreferencesContext(fullPath)); }; /** * Sets tab character width. * Affects any editors that share the same preference location. * @param {number} value * @param {string=} fullPath Path to file to get preference for * @return {boolean} true if value was valid */ Editor.setTabSize = function (value, fullPath) { var options = fullPath && {context: fullPath}; return PreferencesManager.set(TAB_SIZE, value, options); }; /** * Get indent unit * @param {string=} fullPath Path to file to get preference for * @return {number} */ Editor.getTabSize = function (fullPath) { return PreferencesManager.get(TAB_SIZE, _buildPreferencesContext(fullPath)); }; /** * Sets indentation width. * Affects any editors that share the same preference location. * @param {number} value * @param {string=} fullPath Path to file to get preference for * @return {boolean} true if value was valid */ Editor.setSpaceUnits = function (value, fullPath) { var options = fullPath && {context: fullPath}; return PreferencesManager.set(SPACE_UNITS, value, options); }; /** * Get indentation width * @param {string=} fullPath Path to file to get preference for * @return {number} */ Editor.getSpaceUnits = function (fullPath) { return PreferencesManager.get(SPACE_UNITS, _buildPreferencesContext(fullPath)); }; /** * Sets the auto close brackets. * Affects any editors that share the same preference location. * @param {boolean} value * @param {string=} fullPath Path to file to get preference for * @return {boolean} true if value was valid */ Editor.setCloseBrackets = function (value, fullPath) { var options = fullPath && {context: fullPath}; return PreferencesManager.set(CLOSE_BRACKETS, value, options); }; /** * Gets whether the specified or current file uses auto close brackets * @param {string=} fullPath Path to file to get preference for * @return {boolean} */ Editor.getCloseBrackets = function (fullPath) { return PreferencesManager.get(CLOSE_BRACKETS, _buildPreferencesContext(fullPath)); }; /** * Sets show line numbers option. * Affects any editors that share the same preference location. * @param {boolean} value * @param {string=} fullPath Path to file to get preference for * @return {boolean} true if value was valid */ Editor.setShowLineNumbers = function (value, fullPath) { var options = fullPath && {context: fullPath}; return PreferencesManager.set(SHOW_LINE_NUMBERS, value, options); }; /** * Returns true if show line numbers is enabled for the specified or current file * @param {string=} fullPath Path to file to get preference for * @return {boolean} */ Editor.getShowLineNumbers = function (fullPath) { return PreferencesManager.get(SHOW_LINE_NUMBERS, _buildPreferencesContext(fullPath)); }; /** * Sets show active line option. * Affects any editors that share the same preference location. * @param {boolean} value * @param {string=} fullPath Path to file to get preference for * @return {boolean} true if value was valid */ Editor.setShowActiveLine = function (value, fullPath) { return PreferencesManager.set(STYLE_ACTIVE_LINE, value); }; /** * Returns true if show active line is enabled for the specified or current file * @param {string=} fullPath Path to file to get preference for * @return {boolean} */ Editor.getShowActiveLine = function (fullPath) { return PreferencesManager.get(STYLE_ACTIVE_LINE, _buildPreferencesContext(fullPath)); }; /** * Sets word wrap option. * Affects any editors that share the same preference location. * @param {boolean} value * @param {string=} fullPath Path to file to get preference for * @return {boolean} true if value was valid */ Editor.setWordWrap = function (value, fullPath) { var options = fullPath && {context: fullPath}; return PreferencesManager.set(WORD_WRAP, value, options); }; /** * Returns true if word wrap is enabled for the specified or current file * @param {string=} fullPath Path to file to get preference for * @return {boolean} */ Editor.getWordWrap = function (fullPath) { return PreferencesManager.get(WORD_WRAP, _buildPreferencesContext(fullPath)); }; /** * Sets indentLineComment option. * Affects any editors that share the same preference location. * @param {boolean} value * @param {string=} fullPath Path to file to get preference for * @return {boolean} true if value was valid */ Editor.setIndentLineComment = function (value, fullPath) { var options = fullPath && {context: fullPath}; return PreferencesManager.set(INDENT_LINE_COMMENT, value, options); }; /** * Returns true if indentLineComment is enabled for the specified or current file * @param {string=} fullPath Path to file to get preference for * @return {boolean} */ Editor.getIndentLineComment = function (fullPath) { return PreferencesManager.get(INDENT_LINE_COMMENT, _buildPreferencesContext(fullPath)); }; /** * Runs callback for every Editor instance that currently exists * @param {!function(!Editor)} callback */ Editor.forEveryEditor = function (callback) { _instances.forEach(callback); }; /** * @private * Toggles the left padding of all code editors. Used to provide more * space between the code text and the left edge of the editor when * line numbers are hidden. * @param {boolean} showLinePadding */ Editor._toggleLinePadding = function (showLinePadding) { // apply class to all pane DOM nodes var $holders = []; _instances.forEach(function (editor) { var $editorHolder = editor.$el.parent(); if ($holders.indexOf($editorHolder) === -1) { $holders.push($editorHolder); } }); _.each($holders, function ($holder) { $holder.toggleClass("show-line-padding", Boolean(showLinePadding)); }); }; Editor.LINE_NUMBER_GUTTER_PRIORITY = LINE_NUMBER_GUTTER_PRIORITY; Editor.CODE_FOLDING_GUTTER_PRIORITY = CODE_FOLDING_GUTTER_PRIORITY; // Set up listeners for preference changes editorOptions.forEach(function (prefName) { PreferencesManager.on("change", prefName, function () { _instances.forEach(function (editor) { editor._updateOption(prefName); }); }); }); // Define public API exports.Editor = Editor; exports.BOUNDARY_CHECK_NORMAL = BOUNDARY_CHECK_NORMAL; exports.BOUNDARY_IGNORE_TOP = BOUNDARY_IGNORE_TOP; }); ================================================ FILE: src/editor/EditorCommandHandlers.js ================================================ /* * Copyright (c) 2012 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /** * Text-editing commands that apply to whichever Editor is currently focused */ define(function (require, exports, module) { "use strict"; // Load dependent modules var Commands = require("command/Commands"), Strings = require("strings"), Editor = require("editor/Editor").Editor, CommandManager = require("command/CommandManager"), EditorManager = require("editor/EditorManager"), StringUtils = require("utils/StringUtils"), TokenUtils = require("utils/TokenUtils"), CodeMirror = require("thirdparty/CodeMirror/lib/codemirror"), _ = require("thirdparty/lodash"); /** * List of constants */ var DIRECTION_UP = -1; var DIRECTION_DOWN = +1; /** * @private * Creates special regular expressions that matches the line prefix but not the block prefix or suffix * @param {!string} lineSyntax a line comment prefix * @param {!string} blockSyntax a block comment prefix or suffix * @return {RegExp} */ function _createSpecialLineExp(lineSyntax, blockSyntax) { var i, character, escapedCharacter, subExps = [], prevChars = ""; for (i = lineSyntax.length; i < blockSyntax.length; i++) { character = blockSyntax.charAt(i); escapedCharacter = StringUtils.regexEscape(character); subExps.push(prevChars + "[^" + escapedCharacter + "]"); if (prevChars) { subExps.push(prevChars + "$"); } prevChars += escapedCharacter; } return new RegExp("^\\s*" + StringUtils.regexEscape(lineSyntax) + "($|" + subExps.join("|") + ")"); } /** * @private * Creates regular expressions for multiple line comment prefixes * @param {!Array.} prefixes the line comment prefixes * @param {string=} blockPrefix the block comment prefix * @param {string=} blockSuffix the block comment suffix * @return {Array.} */ function _createLineExpressions(prefixes, blockPrefix, blockSuffix) { var lineExp = [], escapedPrefix, nothingPushed; prefixes.forEach(function (prefix) { escapedPrefix = StringUtils.regexEscape(prefix); nothingPushed = true; if (blockPrefix && blockPrefix.indexOf(prefix) === 0) { lineExp.push(_createSpecialLineExp(prefix, blockPrefix)); nothingPushed = false; } if (blockSuffix && blockPrefix !== blockSuffix && blockSuffix.indexOf(prefix) === 0) { lineExp.push(_createSpecialLineExp(prefix, blockSuffix)); nothingPushed = false; } if (nothingPushed) { lineExp.push(new RegExp("^\\s*" + escapedPrefix)); } }); return lineExp; } /** * @private * Returns true if any regular expression matches the given string * @param {!string} string where to look * @param {!Array.} expressions what to look * @return {boolean} */ function _matchExpressions(string, expressions) { return expressions.some(function (exp) { return string.match(exp); }); } /** * @private * Returns the line comment prefix that best matches the string. Since there might be line comment prefixes * that are prefixes of other line comment prefixes, it searches through all and returns the longest line * comment prefix that matches the string. * @param {!string} string where to look * @param {!Array.} expressions the line comment regular expressions * @param {!Array.} prefixes the line comment prefixes * @return {string} */ function _getLinePrefix(string, expressions, prefixes) { var result = null; expressions.forEach(function (exp, index) { if (string.match(exp) && ((result && result.length < prefixes[index].length) || !result)) { result = prefixes[index]; } }); return result; } /** * @private * Searches between startLine and endLine to check if there is at least one line commented with a line comment, and * skips all the block comments. * @param {!Editor} editor * @param {!number} startLine valid line inside the document * @param {!number} endLine valid line inside the document * @param {!Array.} lineExp an array of line comment prefixes regular expressions * @return {boolean} true if there is at least one uncommented line */ function _containsNotLineComment(editor, startLine, endLine, lineExp) { var i, line, containsNotLineComment = false; for (i = startLine; i <= endLine; i++) { line = editor.document.getLine(i); // A line is commented out if it starts with 0-N whitespace chars, then a line comment prefix if (line.match(/\S/) && !_matchExpressions(line, lineExp)) { containsNotLineComment = true; break; } } return containsNotLineComment; } /** * @private * Generates an edit that adds or removes line-comment tokens to all the lines in the selected range, * preserving selection and cursor position. Applies to currently focused Editor. The given selection * must already be a line selection in the form returned by `Editor.convertToLineSelections()`. * * If all non-whitespace lines are already commented out, then we uncomment; otherwise we comment * out. Commenting out adds the prefix at column 0 of every line. Uncommenting removes the first prefix * on each line (if any - empty lines might not have one). * * @param {!Editor} editor * @param {!Array.} prefixes, e.g. ["//"] * @param {string=} blockPrefix, e.g. "" * @param {!Editor} editor The editor to edit within. * @param {!{selectionForEdit: {start:{line:number, ch:number}, end:{line:number, ch:number}, reversed:boolean, primary:boolean}, * selectionsToTrack: Array.<{start:{line:number, ch:number}, end:{line:number, ch:number}, reversed:boolean, primary:boolean}>}} * lineSel A line selection as returned from `Editor.convertToLineSelections()`. `selectionForEdit` is the selection to perform * the line comment operation on, and `selectionsToTrack` are a set of selections associated with this line that need to be * tracked through the edit. * @return {{edit: {text: string, start:{line: number, ch: number}, end:?{line: number, ch: number}}|Array.<{text: string, start:{line: number, ch: number}, end:?{line: number, ch: number}}>, * selection: {start:{line:number, ch:number}, end:{line:number, ch:number}, primary:boolean, reversed: boolean, isBeforeEdit: boolean}>}| * Array.<{start:{line:number, ch:number}, end:{line:number, ch:number}, primary:boolean, reversed: boolean, isBeforeEdit: boolean}>}} * An edit description suitable for including in the edits array passed to `Document.doMultipleEdits()`. */ function _getLineCommentPrefixEdit(editor, prefixes, blockPrefix, blockSuffix, lineSel) { var doc = editor.document, sel = lineSel.selectionForEdit, trackedSels = lineSel.selectionsToTrack, lineExp = _createLineExpressions(prefixes, blockPrefix, blockSuffix), startLine = sel.start.line, endLine = sel.end.line, editGroup = []; // In full-line selection, cursor pos is start of next line - but don't want to modify that line if (sel.end.ch === 0) { endLine--; } // Decide if we're commenting vs. un-commenting // Are there any non-blank lines that aren't commented out? (We ignore blank lines because // some editors like Sublime don't comment them out) var i, line, prefix, commentI, containsNotLineComment = _containsNotLineComment(editor, startLine, endLine, lineExp); if (containsNotLineComment) { // Comment out - prepend the first prefix to each line line = doc.getLine(startLine); var originalCursorPosition = line.search(/\S|$/); var firstCharPosition, cursorPosition = originalCursorPosition; for (i = startLine; i <= endLine; i++) { //check if preference for indent line comment is available otherwise go back to default indentation if (Editor.getIndentLineComment()) { //ignore the first line and recalculate cursor position for first non white space char of every line if (i !== startLine) { line = doc.getLine(i); firstCharPosition = line.search(/\S|$/); } //if the non space first character position is before original start position , put comment at the new position otherwise older pos if (firstCharPosition < originalCursorPosition) { cursorPosition = firstCharPosition; } else { cursorPosition = originalCursorPosition; } editGroup.push({text: prefixes[0], start: {line: i, ch: cursorPosition}}); } else { editGroup.push({text: prefixes[0], start: {line: i, ch: 0}}); } } // Make sure tracked selections include the prefix that was added at start of range _.each(trackedSels, function (trackedSel) { if (trackedSel.start.ch === 0 && CodeMirror.cmpPos(trackedSel.start, trackedSel.end) !== 0) { trackedSel.start = {line: trackedSel.start.line, ch: 0}; trackedSel.end = {line: trackedSel.end.line, ch: (trackedSel.end.line === endLine ? trackedSel.end.ch + prefixes[0].length : 0)}; } else { trackedSel.isBeforeEdit = true; } }); } else { // Uncomment - remove the prefix on each line (if any) for (i = startLine; i <= endLine; i++) { line = doc.getLine(i); prefix = _getLinePrefix(line, lineExp, prefixes); if (prefix) { commentI = line.indexOf(prefix); editGroup.push({text: "", start: {line: i, ch: commentI}, end: {line: i, ch: commentI + prefix.length}}); } } _.each(trackedSels, function (trackedSel) { trackedSel.isBeforeEdit = true; }); } return {edit: editGroup, selection: trackedSels}; } /** * @private * Given a token context it will search backwards to determine if the given token is part of a block comment * that doesn't start at the initial token. This is used to know if a line comment is part of a block comment * or if a block delimiter is the prefix or suffix, by passing a token context at that position. Since the * token context will be moved backwards a lot, it is better to pass a new context. * * @param {!{editor:{CodeMirror}, pos:{ch:{number}, line:{number}}, token:{object}}} ctx token context * @param {!string} prefix the block comment prefix * @param {!string} suffix the block comment suffix * @param {!RegExp} prefixExp a block comment prefix regular expression * @param {!RegExp} suffixExp a block comment suffix regular expression * @param {!Array.} lineExp an array of line comment prefixes regular expressions * @return {boolean} */ function _isPrevTokenABlockComment(ctx, prefix, suffix, prefixExp, suffixExp, lineExp) { // Start searching from the previous token var result = TokenUtils.moveSkippingWhitespace(TokenUtils.movePrevToken, ctx); // Look backwards until we find a none line comment token while (result && _matchExpressions(ctx.token.string, lineExp)) { result = TokenUtils.moveSkippingWhitespace(TokenUtils.movePrevToken, ctx); } // If we are now in a block comment token if (result && ctx.token.type === "comment") { // If it doesnt matches either prefix or suffix, we know is a block comment if (!ctx.token.string.match(prefixExp) && !ctx.token.string.match(suffixExp)) { return true; // We found a line with just a block comment delimiter, but we can't tell which one it is, so we will // keep searching recursively and return the opposite value } else if (prefix === suffix && ctx.token.string.length === prefix.length) { return !_isPrevTokenABlockComment(ctx, prefix, suffix, prefixExp, suffixExp, lineExp); // We can just now the result by checking if the string matches the prefix } else { return ctx.token.string.match(prefixExp); } } return false; } /** * Return the column of the first non whitespace char in the given line. * * @private * @param {!Document} doc * @param {number} lineNum * @returns {number} the column index or null */ function _firstNotWs(doc, lineNum) { var text = doc.getLine(lineNum); if (text === null || text === undefined) { return 0; } return text.search(/\S|$/); } /** * Generates an edit that adds or removes block-comment tokens to the selection, preserving selection * and cursor position. Applies to the currently focused Editor. * * If the selection is inside a block-comment or one block-comment is inside or partially inside the selection * it will uncomment, otherwise it will comment out, unless if there are multiple block comments inside the selection, * where it does nothing. * Commenting out adds the prefix before the selection and the suffix after. * Uncommenting removes them. * * If all the lines inside the selection are line-comment and if the selection is not inside a block-comment, it will * line uncomment all the lines, otherwise it will block comment/uncomment. In the first case, we return null to * indicate to the caller that it needs to handle this selection as a line comment. * * @param {!Editor} editor * @param {!string} prefix, e.g. "" * @param {!Array.} linePrefixes, e.g. ["//"] * @param {!{start:{line:number, ch:number}, end:{line:number, ch:number}, reversed:boolean, primary:boolean}} sel * The selection to block comment/uncomment. * @param {?Array.<{!{start:{line:number, ch:number}, end:{line:number, ch:number}, reversed:boolean, primary:boolean}}>} selectionsToTrack * An array of selections that should be tracked through this edit. * @param {String} command The command callee. It cans be "line" or "block". * @return {{edit: {text: string, start:{line: number, ch: number}, end:?{line: number, ch: number}}|Array.<{text: string, start:{line: number, ch: number}, end:?{line: number, ch: number}}>, * selection: {start:{line:number, ch:number}, end:{line:number, ch:number}, primary:boolean, reversed: boolean, isBeforeEdit: boolean}>}| * Array.<{start:{line:number, ch:number}, end:{line:number, ch:number}, primary:boolean, reversed: boolean, isBeforeEdit: boolean}>}} * An edit description suitable for including in the edits array passed to `Document.doMultipleEdits()`. */ function _getBlockCommentPrefixSuffixEdit(editor, prefix, suffix, linePrefixes, sel, selectionsToTrack, command) { var doc = editor.document, ctx = TokenUtils.getInitialContext(editor._codeMirror, {line: sel.start.line, ch: sel.start.ch}), selEndIndex = editor.indexFromPos(sel.end), lineExp = _createLineExpressions(linePrefixes, prefix, suffix), prefixExp = new RegExp("^" + StringUtils.regexEscape(prefix), "g"), suffixExp = new RegExp(StringUtils.regexEscape(suffix) + "$", "g"), prefixPos = null, suffixPos = null, commentAtStart = true, isBlockComment = false, canComment = false, invalidComment = false, lineUncomment = false, result = true, editGroup = [], edit; var searchCtx, atSuffix, suffixEnd, initialPos, endLine; var indentLineComment = Editor.getIndentLineComment(); function isIndentLineCommand() { return indentLineComment && command === "line"; } if (!selectionsToTrack) { // Track the original selection. selectionsToTrack = [_.cloneDeep(sel)]; } // First move the context to the first none white-space token if (!ctx.token.type && !/\S/.test(ctx.token.string)) { result = TokenUtils.moveSkippingWhitespace(TokenUtils.moveNextToken, ctx); } // Next, move forwards until we find a comment inside the selection while (result && ctx.token.type !== "comment") { result = TokenUtils.moveSkippingWhitespace(TokenUtils.moveNextToken, ctx) && editor.indexFromPos(ctx.pos) <= selEndIndex; commentAtStart = false; } // We are now in a comment, lets check if it is a block or a line comment if (result && ctx.token.type === "comment") { // This token might be at a line comment, but we can't be sure yet if (_matchExpressions(ctx.token.string, lineExp)) { // If the token starts at ch 0 with no starting white spaces, then this might be a block comment or a line // comment over the whole line, and if we found this comment at the start of the selection, we need to search // backwards until we get can tell if we are in a block or a line comment if (ctx.token.start === 0 && !ctx.token.string.match(/^\\s*/) && commentAtStart) { searchCtx = TokenUtils.getInitialContext(editor._codeMirror, {line: ctx.pos.line, ch: ctx.token.start}); isBlockComment = _isPrevTokenABlockComment(searchCtx, prefix, suffix, prefixExp, suffixExp, lineExp); // If not, we already know that is a line comment } else { isBlockComment = false; } // If it was not a line comment, it has to be a block comment } else { isBlockComment = true; // If we are in a line that only has a prefix or a suffix and the prefix and suffix are the same string, // lets find first if this is a prefix or suffix and move the context position to the inside of the block comment. // This means that the token will be anywere inside the block comment, including the lines with the delimiters. // This is required so that later we can find the prefix by moving backwards and the suffix by moving forwards. if (ctx.token.string === prefix && prefix === suffix) { searchCtx = TokenUtils.getInitialContext(editor._codeMirror, {line: ctx.pos.line, ch: ctx.token.start}); atSuffix = _isPrevTokenABlockComment(searchCtx, prefix, suffix, prefixExp, suffixExp, lineExp); if (atSuffix) { TokenUtils.moveSkippingWhitespace(TokenUtils.movePrevToken, ctx); } else { TokenUtils.moveSkippingWhitespace(TokenUtils.moveNextToken, ctx); } } } if (isBlockComment) { // Save the initial position to start searching for the suffix from here initialPos = _.cloneDeep(ctx.pos); // Find the position of the start of the prefix result = true; while (result && !ctx.token.string.match(prefixExp)) { result = TokenUtils.moveSkippingWhitespace(TokenUtils.movePrevToken, ctx); } prefixPos = result && {line: ctx.pos.line, ch: ctx.token.start}; // Restore the context at the initial position to find the position of the start of the suffix, // but only when we found the prefix alone in one line if (ctx.token.string === prefix && prefix === suffix) { ctx = TokenUtils.getInitialContext(editor._codeMirror, _.cloneDeep(initialPos)); } while (result && !ctx.token.string.match(suffixExp)) { result = TokenUtils.moveSkippingWhitespace(TokenUtils.moveNextToken, ctx); } suffixPos = result && {line: ctx.pos.line, ch: ctx.token.end - suffix.length}; // Lets check if there are more comments in the selection. We do nothing if there is one do { result = TokenUtils.moveSkippingWhitespace(TokenUtils.moveNextToken, ctx) && editor.indexFromPos(ctx.pos) <= selEndIndex; } while (result && !ctx.token.string.match(prefixExp)); invalidComment = result && !!ctx.token.string.match(prefixExp); // Make sure we didn't search so far backward or forward that we actually found a block comment // that's entirely before or after the selection. suffixEnd = suffixPos && { line: suffixPos.line, ch: suffixPos.ch + suffix.length }; if ((suffixEnd && CodeMirror.cmpPos(sel.start, suffixEnd) > 0) || (prefixPos && CodeMirror.cmpPos(sel.end, prefixPos) < 0)) { canComment = true; } } else { // In full-line selection, cursor pos is at the start of next line - but don't want to modify that line endLine = sel.end.line; if (sel.end.ch === 0 && editor.hasSelection()) { endLine--; } // Find if all the lines are line-commented. if (!_containsNotLineComment(editor, sel.start.line, endLine, lineExp)) { lineUncomment = true; } else { canComment = true; } } // If not, we can comment } else { canComment = true; } // Make the edit if (invalidComment) { // We don't want to do an edit, but we still want to track selections associated with it. edit = {edit: [], selection: selectionsToTrack}; } else if (lineUncomment) { // Return a null edit. This is a signal to the caller that we should delegate to the // line commenting code. We don't want to just generate the edit here, because the edit // might need to be coalesced with other line-uncomment edits generated by cursors on the // same line. edit = null; } else { // Comment out - add the suffix to the start and the prefix to the end. if (canComment) { var completeLineSel = sel.start.ch === 0 && sel.end.ch === 0 && sel.start.line < sel.end.line; var startCh = _firstNotWs(doc, sel.start.line); if (completeLineSel) { if (isIndentLineCommand()) { var endCh = _firstNotWs(doc, sel.end.line - 1); var useTabChar = Editor.getUseTabChar(editor.document.file.fullPath); var indentChar = useTabChar ? "\t" : " "; editGroup.push({ text: _.repeat(indentChar, endCh) + suffix + "\n", start: {line: sel.end.line, ch: 0} }); editGroup.push({ text: prefix + "\n" + _.repeat(indentChar, startCh), start: {line: sel.start.line, ch: startCh} }); } else { editGroup.push({text: suffix + "\n", start: sel.end}); editGroup.push({text: prefix + "\n", start: sel.start}); } } else { editGroup.push({text: suffix, start: sel.end}); if (isIndentLineCommand()) { editGroup.push({text: prefix, start: { line: sel.start.line, ch: startCh }}); } else { editGroup.push({text: prefix, start: sel.start}); } } // Correct the tracked selections. We can't just use the default selection fixup, // because it will push the end of the selection past the inserted content. Also, // it's possible that we have to deal with tracked selections that might be outside // the bounds of the edit. _.each(selectionsToTrack, function (trackedSel) { function updatePosForEdit(pos) { // First adjust for the suffix insertion. Don't adjust // positions that are exactly at the suffix insertion point. if (CodeMirror.cmpPos(pos, sel.end) > 0) { if (completeLineSel) { pos.line++; } else if (pos.line === sel.end.line) { pos.ch += suffix.length; } } // Now adjust for the prefix insertion. In this case, we do // want to adjust positions that are exactly at the insertion // point. if (CodeMirror.cmpPos(pos, sel.start) >= 0) { if (completeLineSel) { // Just move the line down. pos.line++; } else if (pos.line === sel.start.line && !(isIndentLineCommand() && pos.ch < startCh)) { pos.ch += prefix.length; } } } updatePosForEdit(trackedSel.start); updatePosForEdit(trackedSel.end); }); // Uncomment - remove prefix and suffix. } else { // Find if the prefix and suffix are at the ch 0 and if they are the only thing in the line. // If both are found we assume that a complete line selection comment added new lines, so we remove them. var line = doc.getLine(prefixPos.line).trim(), prefixAtStart = prefixPos.ch === 0 && prefix.length === line.length, prefixIndented = indentLineComment && prefix.length === line.length, suffixAtStart = false, suffixIndented = false; if (suffixPos) { line = doc.getLine(suffixPos.line).trim(); suffixAtStart = suffixPos.ch === 0 && suffix.length === line.length; suffixIndented = indentLineComment && suffix.length === line.length; } // Remove the suffix if there is one if (suffixPos) { if (suffixIndented) { editGroup.push({text: "", start: {line: suffixPos.line, ch: 0}, end: {line: suffixPos.line + 1, ch: 0}}); } else if (prefixAtStart && suffixAtStart) { editGroup.push({text: "", start: suffixPos, end: {line: suffixPos.line + 1, ch: 0}}); } else { editGroup.push({text: "", start: suffixPos, end: {line: suffixPos.line, ch: suffixPos.ch + suffix.length}}); } } // Remove the prefix if (prefixIndented) { editGroup.push({text: "", start: {line: prefixPos.line, ch: 0}, end: {line: prefixPos.line + 1, ch: 0}}); } else if (prefixAtStart && suffixAtStart) { editGroup.push({text: "", start: prefixPos, end: {line: prefixPos.line + 1, ch: 0}}); } else { editGroup.push({text: "", start: prefixPos, end: {line: prefixPos.line, ch: prefixPos.ch + prefix.length}}); } // Don't fix up the tracked selections here - let the edit fix them up. _.each(selectionsToTrack, function (trackedSel) { trackedSel.isBeforeEdit = true; }); } edit = {edit: editGroup, selection: selectionsToTrack}; } return edit; } /** * Generates an edit that adds or removes block-comment tokens to the selection, preserving selection * and cursor position. Applies to the currently focused Editor. The selection must already be a * line selection in the form returned by `Editor.convertToLineSelections()`. * * The implementation uses blockCommentPrefixSuffix, with the exception of the case where * there is no selection on a uncommented and not empty line. In this case the whole lines gets * commented in a block-comment. * * @param {!Editor} editor * @param {!String} prefix * @param {!String} suffix * @param {!{selectionForEdit: {start:{line:number, ch:number}, end:{line:number, ch:number}, reversed:boolean, primary:boolean}, * selectionsToTrack: Array.<{start:{line:number, ch:number}, end:{line:number, ch:number}, reversed:boolean, primary:boolean}>}} * lineSel A line selection as returned from `Editor.convertToLineSelections()`. `selectionForEdit` is the selection to perform * the line comment operation on, and `selectionsToTrack` are a set of selections associated with this line that need to be * tracked through the edit. * @param {String} command The command callee. It cans be "line" or "block". * @return {{edit: {text: string, start:{line: number, ch: number}, end:?{line: number, ch: number}}|Array.<{text: string, start:{line: number, ch: number}, end:?{line: number, ch: number}}>, * selection: {start:{line:number, ch:number}, end:{line:number, ch:number}, primary:boolean, reversed: boolean, isBeforeEdit: boolean}>}| * Array.<{start:{line:number, ch:number}, end:{line:number, ch:number}, primary:boolean, reversed: boolean, isBeforeEdit: boolean}>}} * An edit description suitable for including in the edits array passed to `Document.doMultipleEdits()`. */ function _getLineCommentPrefixSuffixEdit(editor, prefix, suffix, lineSel, command) { var sel = lineSel.selectionForEdit; // For one-line selections, we shrink the selection to exclude the trailing newline. if (sel.end.line === sel.start.line + 1 && sel.end.ch === 0) { sel.end = {line: sel.start.line, ch: editor.document.getLine(sel.start.line).length}; } // Now just run the standard block comment code, but make sure to track any associated selections // that were subsumed into this line selection. return _getBlockCommentPrefixSuffixEdit(editor, prefix, suffix, [], sel, lineSel.selectionsToTrack, command); } /** * @private * Generates an array of edits for toggling line comments on the given selections. * * @param {!Editor} editor The editor to edit within. * @param {Array.<{start:{line:number, ch:number}, end:{line:number, ch:number}, primary:boolean, reversed: boolean, isBeforeEdit: boolean}>} * selections The selections we want to line-comment. * @param {String} command The command callee. It cans be "line" or "block". * @return {Array.<{edit: {text: string, start:{line: number, ch: number}, end:?{line: number, ch: number}}|Array.<{text: string, start:{line: number, ch: number}, end:?{line: number, ch: number}}>, * selection: {start:{line:number, ch:number}, end:{line:number, ch:number}, primary:boolean, reversed: boolean, isBeforeEdit: boolean}>}| * Array.<{start:{line:number, ch:number}, end:{line:number, ch:number}, primary:boolean, reversed: boolean, isBeforeEdit: boolean}>}>} * An array of edit descriptions suitable for including in the edits array passed to `Document.doMultipleEdits()`. */ function _getLineCommentEdits(editor, selections, command) { // We need to expand line selections in order to coalesce cursors on the same line, but we // don't want to merge adjacent line selections. var lineSelections = editor.convertToLineSelections(selections, { mergeAdjacent: false }), edits = []; _.each(lineSelections, function (lineSel) { var sel = lineSel.selectionForEdit, mode = editor.getModeForRange(sel.start, sel.end), edit; if (mode) { var language = editor.document.getLanguage().getLanguageForMode(mode.name || mode); if (language.hasLineCommentSyntax()) { edit = _getLineCommentPrefixEdit(editor, language.getLineCommentPrefixes(), language.getBlockCommentPrefix(), language.getBlockCommentSuffix(), lineSel); } else if (language.hasBlockCommentSyntax()) { edit = _getLineCommentPrefixSuffixEdit(editor, language.getBlockCommentPrefix(), language.getBlockCommentSuffix(), lineSel, command); } } if (!edit) { // Even if we didn't want to do an edit, we still need to track the selection. edit = {selection: lineSel.selectionsToTrack}; } edits.push(edit); }); return edits; } /** * Invokes a language-specific line-comment/uncomment handler * @param {?Editor} editor If unspecified, applies to the currently focused editor */ function lineComment(editor) { editor = editor || EditorManager.getFocusedEditor(); if (!editor) { return; } editor.setSelections(editor.document.doMultipleEdits(_getLineCommentEdits(editor, editor.getSelections(), "line"))); } /** * Invokes a language-specific block-comment/uncomment handler * @param {?Editor} editor If unspecified, applies to the currently focused editor */ function blockComment(editor) { editor = editor || EditorManager.getFocusedEditor(); if (!editor) { return; } var edits = [], lineCommentSels = []; _.each(editor.getSelections(), function (sel) { var mode = editor.getModeForRange(sel.start, sel.end), edit = {edit: [], selection: [sel]}; // default edit in case we don't have a mode for this selection if (mode) { var language = editor.document.getLanguage().getLanguageForMode(mode.name || mode); if (language.hasBlockCommentSyntax()) { // getLineCommentPrefixes always return an array, and will be empty if no line comment syntax is defined edit = _getBlockCommentPrefixSuffixEdit(editor, language.getBlockCommentPrefix(), language.getBlockCommentSuffix(), language.getLineCommentPrefixes(), sel); if (!edit) { // This is only null if the block comment code found that the selection is within a line-commented line. // Add this to the list of line-comment selections we need to handle. Since edit is null, we'll skip // pushing anything onto the edit list for this selection. lineCommentSels.push(sel); } } } if (edit) { edits.push(edit); } }); // Handle any line-comment edits. It's okay if these are out-of-order with the other edits, since // they shouldn't overlap, and `doMultipleEdits()` will take care of sorting the edits so the // selections can be tracked appropriately. edits.push.apply(edits, _getLineCommentEdits(editor, lineCommentSels, "block")); editor.setSelections(editor.document.doMultipleEdits(edits)); } /** * Duplicates the selected text, or current line if no selection. The cursor/selection is left * on the second copy. */ function duplicateText(editor) { editor = editor || EditorManager.getFocusedEditor(); if (!editor) { return; } var selections = editor.getSelections(), delimiter = "", edits = [], rangeSels = [], cursorSels = [], doc = editor.document; // When there are multiple selections, we want to handle all the cursors first (duplicating // their lines), then all the ranges (duplicating the ranges). _.each(selections, function (sel) { if (CodeMirror.cmpPos(sel.start, sel.end) === 0) { cursorSels.push(sel); } else { rangeSels.push(sel); } }); var cursorLineSels = editor.convertToLineSelections(cursorSels); _.each(cursorLineSels, function (lineSel, index) { var sel = lineSel.selectionForEdit; if (sel.end.line === editor.lineCount()) { delimiter = "\n"; } // Don't need to explicitly track selections since we are doing the edits in such a way that // the existing selections will get appropriately updated. edits.push({edit: {text: doc.getRange(sel.start, sel.end) + delimiter, start: sel.start }}); }); _.each(rangeSels, function (sel) { edits.push({edit: {text: doc.getRange(sel.start, sel.end), start: sel.start }}); }); doc.doMultipleEdits(edits); } /** * Deletes the current line if there is no selection or the lines for the selection * (removing the end of line too) */ function deleteCurrentLines(editor) { editor = editor || EditorManager.getFocusedEditor(); if (!editor) { return; } // Walk the selections, calculating the deletion edits we need to do as we go; // document.doMultipleEdits() will take care of adjusting the edit locations when // it actually performs the edits. var doc = editor.document, from, to, lineSelections = editor.convertToLineSelections(editor.getSelections()), edits = []; _.each(lineSelections, function (lineSel, index) { var sel = lineSel.selectionForEdit; from = sel.start; to = sel.end; // this is already at the beginning of the line after the last selected line if (to.line === editor.getLastVisibleLine() + 1) { // Instead of deleting the newline after the last line, delete the newline // before the beginning of the line--unless this is the entire visible content // of the editor, in which case just delete the line content. if (from.line > editor.getFirstVisibleLine()) { from.line -= 1; from.ch = doc.getLine(from.line).length; } to.line -= 1; to.ch = doc.getLine(to.line).length; } // We don't need to track the original selections, since they'll get collapsed as // part of the various deletions that occur. edits.push({edit: {text: "", start: from, end: to}}); }); doc.doMultipleEdits(edits); } /** * Moves the selected text, or current line if no selection. The cursor/selection * moves with the line/lines. * @param {Editor} editor - target editor * @param {Number} direction - direction of the move (-1,+1) => (Up,Down) */ function moveLine(editor, direction) { editor = editor || EditorManager.getFocusedEditor(); if (!editor) { return; } var doc = editor.document, lineSelections = editor.convertToLineSelections(editor.getSelections()), isInlineWidget = !!EditorManager.getFocusedInlineWidget(), firstLine = editor.getFirstVisibleLine(), lastLine = editor.getLastVisibleLine(), totalLines = editor.lineCount(), lineLength = 0, edits = [], newSels = [], pos = {}; _.each(lineSelections, function (lineSel) { var sel = lineSel.selectionForEdit, editGroup = []; // Make the move switch (direction) { case DIRECTION_UP: if (sel.start.line !== firstLine) { var prevText = doc.getRange({ line: sel.start.line - 1, ch: 0 }, sel.start); if (sel.end.line === lastLine + 1) { if (isInlineWidget) { prevText = prevText.substring(0, prevText.length - 1); lineLength = doc.getLine(sel.end.line - 1).length; editGroup.push({text: "\n", start: { line: sel.end.line - 1, ch: lineLength }}); } else { prevText = "\n" + prevText.substring(0, prevText.length - 1); } } editGroup.push({text: "", start: { line: sel.start.line - 1, ch: 0 }, end: sel.start}); editGroup.push({text: prevText, start: { line: sel.end.line - 1, ch: 0 }}); // Make sure CodeMirror hasn't expanded the selection to include // the line we inserted below. _.each(lineSel.selectionsToTrack, function (originalSel) { originalSel.start.line--; originalSel.end.line--; }); edits.push({edit: editGroup, selection: lineSel.selectionsToTrack}); } break; case DIRECTION_DOWN: if (sel.end.line <= lastLine) { var nextText = doc.getRange(sel.end, { line: sel.end.line + 1, ch: 0 }), deletionStart = sel.end; if (sel.end.line === lastLine) { if (isInlineWidget) { if (sel.end.line === totalLines - 1) { nextText += "\n"; } lineLength = doc.getLine(sel.end.line - 1).length; editGroup.push({text: "\n", start: { line: sel.end.line, ch: doc.getLine(sel.end.line).length }}); } else { nextText += "\n"; deletionStart = { line: sel.end.line - 1, ch: doc.getLine(sel.end.line - 1).length }; } } editGroup.push({text: "", start: deletionStart, end: { line: sel.end.line + 1, ch: 0 }}); if (lineLength) { editGroup.push({text: "", start: { line: sel.end.line - 1, ch: lineLength }, end: { line: sel.end.line, ch: 0 }}); } editGroup.push({text: nextText, start: { line: sel.start.line, ch: 0 }}); // In this case, we don't need to track selections, because the edits are done in such a way that // the existing selections will automatically be updated properly by CodeMirror as it does the edits. edits.push({edit: editGroup}); } break; } }); // Make sure selections are correct and primary selection is scrolled into view if (edits.length) { newSels = doc.doMultipleEdits(edits); pos.ch = 0; if (direction === DIRECTION_UP) { editor.setSelections(newSels); pos.line = editor.getSelection().start.line; } else if (direction === DIRECTION_DOWN) { pos.line = editor.getSelection().end.line; } else { console.error("EditorCommandHandler.moveLine() called with invalid argument 'direction' = %d", direction); pos = null; } editor._codeMirror.scrollIntoView(pos); } } /** * Moves the selected text, or current line if no selection, one line up. The cursor/selection * moves with the line/lines. */ function moveLineUp(editor) { moveLine(editor, DIRECTION_UP); } /** * Moves the selected text, or current line if no selection, one line down. The cursor/selection * moves with the line/lines. */ function moveLineDown(editor) { moveLine(editor, DIRECTION_DOWN); } /** * Inserts a new and smart indented line above/below the selected text, or current line if no selection. * The cursor is moved in the new line. * @param {Editor} editor - target editor * @param {Number} direction - direction where to place the new line (-1,+1) => (Up,Down) */ function openLine(editor, direction) { editor = editor || EditorManager.getFocusedEditor(); if (!editor) { return; } var selections = editor.getSelections(), isInlineWidget = !!EditorManager.getFocusedInlineWidget(), lastLine = editor.getLastVisibleLine(), doc = editor.document, edits = [], newSelections, line; // First, insert all the newlines (skipping multiple selections on the same line), // then indent them all. (We can't easily do them all at once, because doMultipleEdits() // won't do the indentation for us, but we want its help tracking any selection changes // as the result of the edits.) // Note that we don't just use `editor.getLineSelections()` here because we don't actually want // to coalesce adjacent selections - we just want to ignore dupes. doc.batchOperation(function () { _.each(selections, function (sel, index) { if (index === 0 || (direction === DIRECTION_UP && sel.start.line > selections[index - 1].start.line) || (direction === DIRECTION_DOWN && sel.end.line > selections[index - 1].end.line)) { // Insert the new line switch (direction) { case DIRECTION_UP: line = sel.start.line; break; case DIRECTION_DOWN: line = sel.end.line; if (!(CodeMirror.cmpPos(sel.start, sel.end) !== 0 && sel.end.ch === 0)) { // If not linewise selection line++; } break; } var insertPos; if (line > lastLine && isInlineWidget) { insertPos = {line: line - 1, ch: doc.getLine(line - 1).length}; } else { insertPos = {line: line, ch: 0}; } // We want the selection after this edit to be right before the \n we just inserted. edits.push({edit: {text: "\n", start: insertPos}, selection: {start: insertPos, end: insertPos, primary: sel.primary}}); } else { // We just want to discard this selection, since we've already operated on the // same line and it would just collapse to the same location. But if this was // primary, make sure the last selection we did operate on ends up as primary. if (sel.primary) { edits[edits.length - 1].selections[0].primary = true; } } }); newSelections = doc.doMultipleEdits(edits, "+input"); // Now indent each added line (which doesn't mess up any line numbers, and // we're going to set the character offset to the last position on each line anyway). _.each(newSelections, function (sel) { // This is a bit of a hack. The document is the one that batches operations, but we want // to use CodeMirror's "smart indent" operation. So we need to use the document's own backing editor's // CodeMirror to do the indentation. A better way to fix this would be to expose this // operation on Document, but I'm not sure we want to sign up for that as a public API. doc._masterEditor._codeMirror.indentLine(sel.start.line, "smart", true); sel.start.ch = null; // last character on line sel.end = sel.start; }); }); editor.setSelections(newSelections); } /** * Inserts a new and smart indented line above the selected text, or current line if no selection. * The cursor is moved in the new line. * @param {Editor} editor - target editor */ function openLineAbove(editor) { openLine(editor, DIRECTION_UP); } /** * Inserts a new and smart indented line below the selected text, or current line if no selection. * The cursor is moved in the new line. * @param {Editor} editor - target editor */ function openLineBelow(editor) { openLine(editor, DIRECTION_DOWN); } /** * Indent a line of text if no selection. Otherwise, indent all lines in selection. */ function indentText() { var editor = EditorManager.getFocusedEditor(); if (!editor) { return; } editor._codeMirror.execCommand("indentMore"); } /** * Unindent a line of text if no selection. Otherwise, unindent all lines in selection. */ function unindentText() { var editor = EditorManager.getFocusedEditor(); if (!editor) { return; } editor._codeMirror.execCommand("indentLess"); } function selectLine(editor) { editor = editor || EditorManager.getFocusedEditor(); if (editor) { // We can just use `convertToLineSelections`, but throw away the original tracked selections and just use the // coalesced selections. editor.setSelections(_.pluck(editor.convertToLineSelections(editor.getSelections(), { expandEndAtStartOfLine: true }), "selectionForEdit")); } } /** * @private * Takes the current selection and splits each range into separate selections, one per line. * @param {!Editor} editor The editor to operate on. */ function splitSelIntoLines(editor) { editor = editor || EditorManager.getFocusedEditor(); if (editor) { editor._codeMirror.execCommand("splitSelectionByLine"); } } /** * @private * Adds a cursor on the next/previous line after/before each selected range to the selection. * @param {!Editor} editor The editor to operate on. * @param {number} dir The direction to add - 1 is down, -1 is up. */ function addCursorToSelection(editor, dir) { editor = editor || EditorManager.getFocusedEditor(); if (editor) { var origSels = editor.getSelections(), newSels = []; _.each(origSels, function (sel) { var pos, colOffset; if ((dir === -1 && sel.start.line > editor.getFirstVisibleLine()) || (dir === 1 && sel.end.line < editor.getLastVisibleLine())) { // Add a new cursor on the next line up/down. It's okay if it overlaps another selection, because CM // will take care of throwing it away in that case. It will also take care of clipping the char position // to the end of the new line if the line is shorter. pos = _.clone(dir === -1 ? sel.start : sel.end); // get sel column of current selection colOffset = editor.getColOffset(pos); pos.line += dir; // translate column to ch in line of new selection pos.ch = editor.getCharIndexForColumn(pos.line, colOffset); // If this is the primary selection, we want the new cursor we're adding to become the // primary selection. newSels.push({start: pos, end: pos, primary: sel.primary}); sel.primary = false; } }); // CM will take care of sorting the selections. editor.setSelections(origSels.concat(newSels)); } } /** * @private * Adds a cursor on the previous line before each selected range to the selection. * @param {!Editor} editor The editor to operate on. */ function addCursorToPrevLine(editor) { addCursorToSelection(editor, -1); } /** * @private * Adds a cursor on the next line after each selected range to the selection. * @param {!Editor} editor The editor to operate on. */ function addCursorToNextLine(editor) { addCursorToSelection(editor, 1); } function handleUndoRedo(operation) { var editor = EditorManager.getFocusedEditor(); var result = new $.Deferred(); if (editor) { editor[operation](); result.resolve(); } else { result.reject(); } return result.promise(); } function handleUndo() { return handleUndoRedo("undo"); } function handleRedo() { return handleUndoRedo("redo"); } function _handleSelectAll() { var result = new $.Deferred(), editor = EditorManager.getFocusedEditor(); if (editor) { editor.selectAllNoScroll(); result.resolve(); } else { result.reject(); // command not handled } return result.promise(); } function _execCommand(cmd) { window.document.execCommand(cmd); } function _execCommandCut() { _execCommand("cut"); } function _execCommandCopy() { _execCommand("copy"); } function _execCommandPaste() { _execCommand("paste"); } // Register commands CommandManager.register(Strings.CMD_INDENT, Commands.EDIT_INDENT, indentText); CommandManager.register(Strings.CMD_UNINDENT, Commands.EDIT_UNINDENT, unindentText); CommandManager.register(Strings.CMD_COMMENT, Commands.EDIT_LINE_COMMENT, lineComment); CommandManager.register(Strings.CMD_BLOCK_COMMENT, Commands.EDIT_BLOCK_COMMENT, blockComment); CommandManager.register(Strings.CMD_DUPLICATE, Commands.EDIT_DUPLICATE, duplicateText); CommandManager.register(Strings.CMD_DELETE_LINES, Commands.EDIT_DELETE_LINES, deleteCurrentLines); CommandManager.register(Strings.CMD_LINE_UP, Commands.EDIT_LINE_UP, moveLineUp); CommandManager.register(Strings.CMD_LINE_DOWN, Commands.EDIT_LINE_DOWN, moveLineDown); CommandManager.register(Strings.CMD_OPEN_LINE_ABOVE, Commands.EDIT_OPEN_LINE_ABOVE, openLineAbove); CommandManager.register(Strings.CMD_OPEN_LINE_BELOW, Commands.EDIT_OPEN_LINE_BELOW, openLineBelow); CommandManager.register(Strings.CMD_SELECT_LINE, Commands.EDIT_SELECT_LINE, selectLine); CommandManager.register(Strings.CMD_SPLIT_SEL_INTO_LINES, Commands.EDIT_SPLIT_SEL_INTO_LINES, splitSelIntoLines); CommandManager.register(Strings.CMD_ADD_CUR_TO_NEXT_LINE, Commands.EDIT_ADD_CUR_TO_NEXT_LINE, addCursorToNextLine); CommandManager.register(Strings.CMD_ADD_CUR_TO_PREV_LINE, Commands.EDIT_ADD_CUR_TO_PREV_LINE, addCursorToPrevLine); CommandManager.register(Strings.CMD_UNDO, Commands.EDIT_UNDO, handleUndo); CommandManager.register(Strings.CMD_REDO, Commands.EDIT_REDO, handleRedo); CommandManager.register(Strings.CMD_CUT, Commands.EDIT_CUT, _execCommandCut); CommandManager.register(Strings.CMD_COPY, Commands.EDIT_COPY, _execCommandCopy); CommandManager.register(Strings.CMD_PASTE, Commands.EDIT_PASTE, _execCommandPaste); CommandManager.register(Strings.CMD_SELECT_ALL, Commands.EDIT_SELECT_ALL, _handleSelectAll); }); ================================================ FILE: src/editor/EditorManager.js ================================================ /* * Copyright (c) 2012 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /** * EditorManager owns the UI for the editor area. This essentially mirrors the 'current document' * property maintained by DocumentManager's model. * * Note that there is a little bit of unusual overlap between EditorManager and DocumentManager: * because the Document state is actually stored in the CodeMirror editor UI, DocumentManager is * not a pure headless model. Each Document encapsulates an editor instance, and thus EditorManager * must have some knowledge about Document's internal state (we access its _editor property). * * This module dispatches the following events: * - activeEditorChange -- Fires after the active editor (full or inline). * * Doesn't fire when editor temporarily loses focus to a non-editor * control (e.g. search toolbar or modal dialog, or window deactivation). * * Does fire when focus moves between inline editor and its full-size container. * * This event tracks `MainViewManagers's `currentFileChange` event and all editor * objects "focus" event. * * (e, editorGainingFocus:editor, editorLosingFocus:editor) * * The 2nd arg to the listener is which Editor became active; the 3rd arg is * which Editor is deactivated as a result. Either one may be null. * NOTE (#1257): `getFocusedEditor()` sometimes lags behind this event. Listeners * should use the arguments or call `getActiveEditor()` to reliably see which Editor * just gained focus. */ define(function (require, exports, module) { "use strict"; // Load dependent modules var Commands = require("command/Commands"), EventDispatcher = require("utils/EventDispatcher"), WorkspaceManager = require("view/WorkspaceManager"), PreferencesManager = require("preferences/PreferencesManager"), CommandManager = require("command/CommandManager"), DocumentManager = require("document/DocumentManager"), MainViewManager = require("view/MainViewManager"), ViewStateManager = require("view/ViewStateManager"), PerfUtils = require("utils/PerfUtils"), Editor = require("editor/Editor").Editor, InlineTextEditor = require("editor/InlineTextEditor").InlineTextEditor, Strings = require("strings"), LanguageManager = require("language/LanguageManager"), DeprecationWarning = require("utils/DeprecationWarning"); /** * Currently focused Editor (full-size, inline, or otherwise) * @type {?Editor} * @private */ var _lastFocusedEditor = null; /** * Registered inline-editor widget providers sorted descending by priority. * @see {@link #registerInlineEditProvider}. * @type {Array.<{priority:number, provider:function(...)}>} * @private */ var _inlineEditProviders = []; /** * Registered inline documentation widget providers sorted descending by priority. * @see {@link #registerInlineDocsProvider}. * @type {Array.<{priority:number, provider:function(...)}>} * @private */ var _inlineDocsProviders = []; /** * DOM element to house any hidden editors created soley for inline widgets * @private * @type {jQuery} */ var _$hiddenEditorsContainer; /** * Retrieves the visible full-size Editor for the currently opened file in the ACTIVE_PANE * @return {?Editor} editor of the current view or null */ function getCurrentFullEditor() { var currentPath = MainViewManager.getCurrentlyViewedPath(MainViewManager.ACTIVE_PANE), doc = currentPath && DocumentManager.getOpenDocumentForPath(currentPath); return doc && doc._masterEditor; } /** * Updates _viewStateCache from the given editor's actual current state * @private * @param {!Editor} editor - editor to cache data for */ function _saveEditorViewState(editor) { ViewStateManager.updateViewState(editor); } /** * Updates _viewStateCache from the given editor's actual current state * @param {!Editor} editor - editor restore cached data * @private */ function _restoreEditorViewState(editor) { // We want to ignore the current state of the editor, so don't call __getViewState() var viewState = ViewStateManager.getViewState(editor.document.file); if (viewState) { editor.restoreViewState(viewState); } } /** * Editor focus handler to change the currently active editor * @private * @param {?Editor} current - the editor that will be the active editor */ function _notifyActiveEditorChanged(current) { // Skip if the Editor that gained focus was already the most recently focused editor. // This may happen e.g. if the window loses then regains focus. if (_lastFocusedEditor === current) { return; } var previous = _lastFocusedEditor; _lastFocusedEditor = current; exports.trigger("activeEditorChange", current, previous); } /** * Current File Changed handler * MainViewManager dispatches a "currentFileChange" event whenever the currently viewed * file changes. Which could mean that the previously viewed file has been closed or a * non-editor view (image) has been given focus. _notifyAcitveEditorChanged is also hooked * up to editor.focus to handle focus events for editors which handles changing focus between * two editors but, because editormanager maintains a "_lastFocusedEditor" state, we have to * "nullify" that state whenever the focus goes to a non-editor or when the current editor is closed * @private * @param {!jQuery.Event} e - event * @param {?File} file - current file (can be null) */ function _handleCurrentFileChange(e, file) { var doc = file && DocumentManager.getOpenDocumentForPath(file.fullPath); _notifyActiveEditorChanged(doc && doc._masterEditor); } /** * Creates a new Editor bound to the given Document. * The editor is appended to the given container as a visible child. * @private * @param {!Document} doc Document for the Editor's content * @param {!boolean} makeMasterEditor If true, the Editor will set itself as the private "master" * Editor for the Document. If false, the Editor will attach to the Document as a "slave." * @param {!jQueryObject} container Container to add the editor to. * @param {{startLine: number, endLine: number}=} range If specified, range of lines within the document * to display in this editor. Inclusive. * @param {!Object} editorOptions If specified, contains editor options that can be passed to CodeMirror * @return {Editor} the newly created editor. */ function _createEditorForDocument(doc, makeMasterEditor, container, range, editorOptions) { var editor = new Editor(doc, makeMasterEditor, container, range, editorOptions); editor.on("focus", function () { _notifyActiveEditorChanged(editor); }); editor.on("beforeDestroy", function () { if (editor.$el.is(":visible")) { _saveEditorViewState(editor); } }); return editor; } /** * @private * Finds an inline widget provider from the given list that can offer a widget for the current cursor * position, and once the widget has been created inserts it into the editor. * * @param {!Editor} editor The host editor * @param {Array.<{priority:number, provider:function(...)}>} providers * prioritized list of providers * @param {string=} defaultErrorMsg Default message to display if no providers return non-null * @return {$.Promise} a promise that will be resolved when an InlineWidget * is created or rejected if no inline providers have offered one. */ function _openInlineWidget(editor, providers, defaultErrorMsg) { PerfUtils.markStart(PerfUtils.INLINE_WIDGET_OPEN); // Run through inline-editor providers until one responds var pos = editor.getCursorPos(), inlinePromise, i, result = new $.Deferred(), errorMsg, providerRet; // Query each provider in priority order. Provider may return: // 1. `null` to indicate it does not apply to current cursor position // 2. promise that should resolve to an InlineWidget // 3. string which indicates provider does apply to current cursor position, // but reason it could not create InlineWidget // // Keep looping until a provider is found. If a provider is not found, // display the highest priority error message that was found, otherwise display // default error message for (i = 0; i < providers.length && !inlinePromise; i++) { var provider = providers[i].provider; providerRet = provider(editor, pos); if (providerRet) { if (providerRet.hasOwnProperty("done")) { inlinePromise = providerRet; } else if (!errorMsg && typeof (providerRet) === "string") { errorMsg = providerRet; } } } // Use default error message if none other provided errorMsg = errorMsg || defaultErrorMsg; // If one of them will provide a widget, show it inline once ready if (inlinePromise) { inlinePromise.done(function (inlineWidget) { editor.addInlineWidget(pos, inlineWidget).done(function () { PerfUtils.addMeasurement(PerfUtils.INLINE_WIDGET_OPEN); result.resolve(); }); }).fail(function () { // terminate timer that was started above PerfUtils.finalizeMeasurement(PerfUtils.INLINE_WIDGET_OPEN); editor.displayErrorMessageAtCursor(errorMsg); result.reject(); }); } else { // terminate timer that was started above PerfUtils.finalizeMeasurement(PerfUtils.INLINE_WIDGET_OPEN); editor.displayErrorMessageAtCursor(errorMsg); result.reject(); } return result.promise(); } /** * Closes any focused inline widget. Else, asynchronously asks providers to create one. * * @param {Array.<{priority:number, provider:function(...)}>} providers * prioritized list of providers * @param {string=} errorMsg Default message to display if no providers return non-null * @return {!Promise} A promise resolved with true if an inline widget is opened or false * when closed. Rejected if there is neither an existing widget to close nor a provider * willing to create a widget (or if no editor is open). */ function _toggleInlineWidget(providers, errorMsg) { var result = new $.Deferred(); var currentEditor = getCurrentFullEditor(); if (currentEditor) { var inlineWidget = currentEditor.getFocusedInlineWidget(); if (inlineWidget) { // an inline widget's editor has focus, so close it PerfUtils.markStart(PerfUtils.INLINE_WIDGET_CLOSE); inlineWidget.close().done(function () { PerfUtils.addMeasurement(PerfUtils.INLINE_WIDGET_CLOSE); // return a resolved promise to CommandManager result.resolve(false); }); } else { // main editor has focus, so create an inline editor _openInlineWidget(currentEditor, providers, errorMsg).done(function () { result.resolve(true); }).fail(function () { result.reject(); }); } } else { // Can not open an inline editor without a host editor result.reject(); } return result.promise(); } /** * Inserts a prioritized provider object into the array in sorted (descending) order. * @private * @param {Array.<{priority:number, provider:function(...)}>} array * @param {number} priority * @param {function(...)} provider */ function _insertProviderSorted(array, provider, priority) { var index, prioritizedProvider = { priority: priority, provider: provider }; for (index = 0; index < array.length; index++) { if (array[index].priority < priority) { break; } } array.splice(index, 0, prioritizedProvider); } /** * Creates a hidden, unattached master editor that is needed when a document is created for the * sole purpose of creating an inline editor so operations that require a master editor can be performed * Only called from Document._ensureMasterEditor() * The editor view is placed in a hidden part of the DOM but can later be moved to a visible pane * when the document is opened using pane.addView() * @param {!Document} doc - document to create a hidden editor for */ function _createUnattachedMasterEditor(doc) { // attach to the hidden containers DOM node if necessary if (!_$hiddenEditorsContainer) { _$hiddenEditorsContainer = $("#hidden-editors"); } // Create an editor var editor = _createEditorForDocument(doc, true, _$hiddenEditorsContainer); // and hide it editor.setVisible(false); } /** * Removes the given widget UI from the given hostEditor (agnostic of what the widget's content * is). The widget's onClosed() callback will be run as a result. * @param {!Editor} hostEditor The editor containing the widget. * @param {!InlineWidget} inlineWidget The inline widget to close. * @return {$.Promise} A promise that's resolved when the widget is fully closed. */ function closeInlineWidget(hostEditor, inlineWidget) { // If widget has focus, return it to the hostEditor & move the cursor to where the inline used to be if (inlineWidget.hasFocus()) { // Place cursor back on the line just above the inline (the line from which it was opened) // If cursor's already on that line, leave it be to preserve column position var widgetLine = hostEditor._codeMirror.getLineNumber(inlineWidget.info.line); var cursorLine = hostEditor.getCursorPos().line; if (cursorLine !== widgetLine) { hostEditor.setCursorPos({ line: widgetLine, pos: 0 }); } hostEditor.focus(); } return hostEditor.removeInlineWidget(inlineWidget); } /** * Registers a new inline editor provider. When Quick Edit is invoked each registered provider is * asked if it wants to provide an inline editor given the current editor and cursor location. * An optional priority parameter is used to give providers with higher priority an opportunity * to provide an inline editor before providers with lower priority. * * @param {function(!Editor, !{line:number, ch:number}):?($.Promise|string)} provider * @param {number=} priority * The provider returns a promise that will be resolved with an InlineWidget, or returns a string * indicating why the provider cannot respond to this case (or returns null to indicate no reason). */ function registerInlineEditProvider(provider, priority) { if (priority === undefined) { priority = 0; } _insertProviderSorted(_inlineEditProviders, provider, priority); } /** * Registers a new inline docs provider. When Quick Docs is invoked each registered provider is * asked if it wants to provide inline docs given the current editor and cursor location. * An optional priority parameter is used to give providers with higher priority an opportunity * to provide an inline editor before providers with lower priority. * * @param {function(!Editor, !{line:number, ch:number}):?($.Promise|string)} provider * @param {number=} priority * The provider returns a promise that will be resolved with an InlineWidget, or returns a string * indicating why the provider cannot respond to this case (or returns null to indicate no reason). */ function registerInlineDocsProvider(provider, priority) { if (priority === undefined) { priority = 0; } _insertProviderSorted(_inlineDocsProviders, provider, priority); } /** * @private * Given a host editor, return a list of all Editors in all its open inline widgets. (Ignoring * any other inline widgets that might be open but don't contain Editors). * @param {!Editor} hostEditor * @return {Array.} * */ function getInlineEditors(hostEditor) { var inlineEditors = []; if (hostEditor) { hostEditor.getInlineWidgets().forEach(function (widget) { if (widget instanceof InlineTextEditor && widget.editor) { inlineEditors.push(widget.editor); } }); } return inlineEditors; } /** * @private * Creates a new "full-size" (not inline) Editor for the given Document, and sets it as the * Document's master backing editor. The editor is not yet visible; * Semi-private: should only be called within this module or by Document. * @param {!Document} document Document whose main/full Editor to create * @param {!Pane} pane Pane in which the editor will be hosted * @param {!Object} editorOptions If specified, contains editor options that * can be passed to CodeMirror * @return {!Editor} */ function _createFullEditorForDocument(document, pane, editorOptions) { // Create editor; make it initially invisible var editor = _createEditorForDocument(document, true, pane.$content, undefined, editorOptions); editor.setVisible(false); pane.addView(editor); exports.trigger("_fullEditorCreatedForDocument", document, editor, pane.id); return editor; } /** * Creates a new inline Editor instance for the given Document. * The editor is not yet visible or attached to a host editor. * @param {!Document} doc Document for the Editor's content * @param {?{startLine:Number, endLine:Number}} range If specified, all lines outside the given * range are hidden from the editor. Range is inclusive. Line numbers start at 0. * @param {HTMLDivContainer} inlineContent * @param {function(inlineWidget)} closeThisInline * * @return {{content:DOMElement, editor:Editor}} */ function createInlineEditorForDocument(doc, range, inlineContent) { // Hide the container for the editor before creating it so that CodeMirror doesn't do extra work // when initializing the document. When we construct the editor, we have to set its text and then // set the (small) visible range that we show in the editor. If the editor is visible, CM has to // render a large portion of the document before setting the visible range. By hiding the editor // first and showing it after the visible range is set, we avoid that initial render. $(inlineContent).hide(); var inlineEditor = _createEditorForDocument(doc, false, inlineContent, range); inlineEditor._hostEditor = getCurrentFullEditor(); $(inlineContent).show(); return { content: inlineContent, editor: inlineEditor }; } /** * Returns focus to the last visible editor that had focus. If no editor visible, does nothing. * This function should be called to restore editor focus after it has been temporarily * removed. For example, after a dialog with editable text is closed. */ function focusEditor() { DeprecationWarning.deprecationWarning("Use MainViewManager.focusActivePane() instead of EditorManager.focusEditor().", true); MainViewManager.focusActivePane(); } /** * @deprecated * resizes the editor */ function resizeEditor() { DeprecationWarning.deprecationWarning("Use WorkspaceManager.recomputeLayout() instead of EditorManager.resizeEditor().", true); WorkspaceManager.recomputeLayout(); } /** * Create and/or show the editor for the specified document * @param {!Document} document - document to edit * @param {!Pane} pane - pane to show it in * @param {!Object} editorOptions - If specified, contains * editor options that can be passed to CodeMirror * @private */ function _showEditor(document, pane, editorOptions) { // Ensure a main editor exists for this document to show in the UI var createdNewEditor = false, editor = document._masterEditor; // Check if a master editor is not set already or the current master editor doesn't belong // to the pane container requested - to support creation of multiple full editors // This check is required as _masterEditor is the active full editor for the document // and there can be existing full editor created for other panes if (editor && editor._paneId && editor._paneId !== pane.id) { editor = document._checkAssociatedEditorForPane(pane.id); } if (!editor) { // Performance (see #4757) Chrome wastes time messing with selection // that will just be changed at end, so clear it for now if (window.getSelection && window.getSelection().empty) { // Chrome window.getSelection().empty(); } // Editor doesn't exist: populate a new Editor with the text editor = _createFullEditorForDocument(document, pane, editorOptions); createdNewEditor = true; } else if (editor.$el.parent()[0] !== pane.$content[0]) { // editor does exist but is not a child of the pane so add it to the // pane (which will switch the view's container as well) pane.addView(editor); } // show the view pane.showView(editor); if (MainViewManager.getActivePaneId() === pane.id) { // give it focus editor.focus(); } if (createdNewEditor) { _restoreEditorViewState(editor); } } /** * @deprecated use MainViewManager.getCurrentlyViewedFile() instead * @return {string=} path of the file currently viewed in the active, full sized editor or null when there is no active editor */ function getCurrentlyViewedPath() { DeprecationWarning.deprecationWarning("Use MainViewManager.getCurrentlyViewedFile() instead of EditorManager.getCurrentlyViewedPath().", true); // We only want to return a path of a document object // not other things like images, etc... var currentPath = MainViewManager.getCurrentlyViewedPath(MainViewManager.ACTIVE_PANE), doc; if (currentPath) { doc = DocumentManager.getOpenDocumentForPath(currentPath); } if (doc) { return currentPath; } return null; } /** * @deprecated There is no equivalent API moving forward. * Use MainViewManager._initialize() from a unit test to create a Main View attached to a specific DOM element */ function setEditorHolder() { throw new Error("EditorManager.setEditorHolder() has been removed."); } /** * @deprecated Register a View Factory instead * @see MainViewFactory::#registerViewFactory */ function registerCustomViewer() { throw new Error("EditorManager.registerCustomViewer() has been removed."); } /** * Determines if the file can be opened in an editor * @param {!string} fullPath - file to be opened * @return {boolean} true if the file can be opened in an editor, false if not */ function canOpenPath(fullPath) { return !LanguageManager.getLanguageForPath(fullPath).isBinary(); } /** * Opens the specified document in the given pane * @param {!Document} doc - the document to open * @param {!Pane} pane - the pane to open the document in * @param {!Object} editorOptions - If specified, contains * editor options that can be passed to CodeMirror * @return {boolean} true if the file can be opened, false if not */ function openDocument(doc, pane, editorOptions) { var perfTimerName = PerfUtils.markStart("EditorManager.openDocument():\t" + (!doc || doc.file.fullPath)); if (doc && pane) { _showEditor(doc, pane, editorOptions); } PerfUtils.addMeasurement(perfTimerName); } /** * Returns the currently focused inline widget, if any. * @return {?InlineWidget} */ function getFocusedInlineWidget() { var currentEditor = getCurrentFullEditor(); if (currentEditor) { return currentEditor.getFocusedInlineWidget(); } return null; } /** * Returns the focused Editor within an inline text editor, or null if something else has focus * @return {?Editor} */ function _getFocusedInlineEditor() { var focusedWidget = getFocusedInlineWidget(); if (focusedWidget instanceof InlineTextEditor) { return focusedWidget.getFocusedEditor(); } return null; } /** * Returns the currently focused editor instance (full-sized OR inline editor). * This function is similar to getActiveEditor(), with one main difference: this * function will only return editors that currently have focus, whereas * getActiveEditor() will return the last visible editor that was given focus (but * may not currently have focus because, for example, a dialog with editable text * is open). * @return {?Editor} */ function getFocusedEditor() { var currentEditor = getCurrentFullEditor(); if (currentEditor) { // See if any inlines have focus var focusedInline = _getFocusedInlineEditor(); if (focusedInline) { return focusedInline; } // otherwise, see if full-sized editor has focus if (currentEditor.hasFocus()) { return currentEditor; } } return null; } /** * Returns the current active editor (full-sized OR inline editor). This editor may not * have focus at the moment, but it is visible and was the last editor that was given * focus. Returns null if no editors are active. * @see #getFocusedEditor * @return {?Editor} */ function getActiveEditor() { return _lastFocusedEditor; } /** * file removed from pane handler. * @param {jQuery.Event} e * @param {File|Array.} removedFiles - file, path or array of files or paths that are being removed */ function _handleRemoveFromPaneView(e, removedFiles) { var handleFileRemoved = function (file) { var doc = DocumentManager.getOpenDocumentForPath(file.fullPath); if (doc) { MainViewManager._destroyEditorIfNotNeeded(doc); } }; // when files are removed from a pane then // we should destroy any unnecssary views if ($.isArray(removedFiles)) { removedFiles.forEach(function (removedFile) { handleFileRemoved(removedFile); }); } else { handleFileRemoved(removedFiles); } } // Set up event dispatching EventDispatcher.makeEventDispatcher(exports); // File-based preferences handling exports.on("activeEditorChange", function (e, current) { if (current && current.document && current.document.file) { PreferencesManager._setCurrentFile(current.document.file.fullPath); } }); // Initialize: command handlers CommandManager.register(Strings.CMD_TOGGLE_QUICK_EDIT, Commands.TOGGLE_QUICK_EDIT, function () { return _toggleInlineWidget(_inlineEditProviders, Strings.ERROR_QUICK_EDIT_PROVIDER_NOT_FOUND); }); CommandManager.register(Strings.CMD_TOGGLE_QUICK_DOCS, Commands.TOGGLE_QUICK_DOCS, function () { return _toggleInlineWidget(_inlineDocsProviders, Strings.ERROR_QUICK_DOCS_PROVIDER_NOT_FOUND); }); MainViewManager.on("currentFileChange", _handleCurrentFileChange); MainViewManager.on("workingSetRemove workingSetRemoveList", _handleRemoveFromPaneView); // For unit tests and internal use only exports._createFullEditorForDocument = _createFullEditorForDocument; exports._notifyActiveEditorChanged = _notifyActiveEditorChanged; // Internal Use only exports._saveEditorViewState = _saveEditorViewState; exports._createUnattachedMasterEditor = _createUnattachedMasterEditor; // Define public API exports.createInlineEditorForDocument = createInlineEditorForDocument; exports.getFocusedInlineWidget = getFocusedInlineWidget; exports.getInlineEditors = getInlineEditors; exports.closeInlineWidget = closeInlineWidget; exports.openDocument = openDocument; exports.canOpenPath = canOpenPath; // Convenience Methods exports.getActiveEditor = getActiveEditor; exports.getCurrentFullEditor = getCurrentFullEditor; exports.getFocusedEditor = getFocusedEditor; exports.registerInlineEditProvider = registerInlineEditProvider; exports.registerInlineDocsProvider = registerInlineDocsProvider; // Deprecated exports.registerCustomViewer = registerCustomViewer; exports.resizeEditor = resizeEditor; exports.focusEditor = focusEditor; exports.getCurrentlyViewedPath = getCurrentlyViewedPath; exports.setEditorHolder = setEditorHolder; }); ================================================ FILE: src/editor/EditorOptionHandlers.js ================================================ /* * Copyright (c) 2013 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ define(function (require, exports, module) { "use strict"; var AppInit = require("utils/AppInit"), Editor = require("editor/Editor").Editor, Commands = require("command/Commands"), CommandManager = require("command/CommandManager"), PreferencesManager = require("preferences/PreferencesManager"), Strings = require("strings"), _ = require("thirdparty/lodash"); // Constants for the preferences referred to in this file var SHOW_LINE_NUMBERS = "showLineNumbers", STYLE_ACTIVE_LINE = "styleActiveLine", WORD_WRAP = "wordWrap", CLOSE_BRACKETS = "closeBrackets", AUTO_HIDE_SEARCH = "autoHideSearch"; /** * @private * * Maps from preference names to the command names needed to update the checked status. */ var _optionMapping = {}; _optionMapping[SHOW_LINE_NUMBERS] = Commands.TOGGLE_LINE_NUMBERS; _optionMapping[STYLE_ACTIVE_LINE] = Commands.TOGGLE_ACTIVE_LINE; _optionMapping[WORD_WRAP] = Commands.TOGGLE_WORD_WRAP; _optionMapping[CLOSE_BRACKETS] = Commands.TOGGLE_CLOSE_BRACKETS; _optionMapping[AUTO_HIDE_SEARCH] = Commands.TOGGLE_SEARCH_AUTOHIDE; /** * @private * * Updates the command checked status based on the preference name given. * * @param {string} name Name of preference that has changed */ function _updateCheckedState(name) { var mapping = _optionMapping[name]; if (!mapping) { return; } CommandManager.get(mapping).setChecked(PreferencesManager.get(name)); } // Listen to preference changes for the preferences we care about Object.keys(_optionMapping).forEach(function (preference) { PreferencesManager.on("change", preference, function () { _updateCheckedState(preference); }); }); /** * @private * Creates a function that will toggle the named preference. * * @param {string} prefName Name of preference that should be toggled by the function */ function _getToggler(prefName) { return function () { PreferencesManager.set(prefName, !PreferencesManager.get(prefName)); }; } function _init() { _.each(_optionMapping, function (commandName, prefName) { CommandManager.get(commandName).setChecked(PreferencesManager.get(prefName)); }); if (!Editor.getShowLineNumbers()) { Editor._toggleLinePadding(true); } } CommandManager.register(Strings.CMD_TOGGLE_LINE_NUMBERS, Commands.TOGGLE_LINE_NUMBERS, _getToggler(SHOW_LINE_NUMBERS)); CommandManager.register(Strings.CMD_TOGGLE_ACTIVE_LINE, Commands.TOGGLE_ACTIVE_LINE, _getToggler(STYLE_ACTIVE_LINE)); CommandManager.register(Strings.CMD_TOGGLE_WORD_WRAP, Commands.TOGGLE_WORD_WRAP, _getToggler(WORD_WRAP)); CommandManager.register(Strings.CMD_TOGGLE_CLOSE_BRACKETS, Commands.TOGGLE_CLOSE_BRACKETS, _getToggler(CLOSE_BRACKETS)); CommandManager.register(Strings.CMD_TOGGLE_SEARCH_AUTOHIDE, Commands.TOGGLE_SEARCH_AUTOHIDE, _getToggler(AUTO_HIDE_SEARCH)); AppInit.htmlReady(_init); }); ================================================ FILE: src/editor/EditorStatusBar.js ================================================ /* * Copyright (c) 2013 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /** * Manages parts of the status bar related to the current editor's state. */ define(function (require, exports, module) { "use strict"; // Load dependent modules var _ = require("thirdparty/lodash"), AnimationUtils = require("utils/AnimationUtils"), AppInit = require("utils/AppInit"), DropdownButton = require("widgets/DropdownButton").DropdownButton, EditorManager = require("editor/EditorManager"), MainViewManager = require("view/MainViewManager"), Editor = require("editor/Editor").Editor, KeyEvent = require("utils/KeyEvent"), LanguageManager = require("language/LanguageManager"), PreferencesManager = require("preferences/PreferencesManager"), StatusBar = require("widgets/StatusBar"), Strings = require("strings"), FileUtils = require("file/FileUtils"), InMemoryFile = require("document/InMemoryFile"), Dialogs = require("widgets/Dialogs"), DefaultDialogs = require("widgets/DefaultDialogs"), ProjectManager = require("project/ProjectManager"), Async = require("utils/Async"), FileSystem = require("filesystem/FileSystem"), CommandManager = require("command/CommandManager"), Commands = require("command/Commands"), DocumentManager = require("document/DocumentManager"), StringUtils = require("utils/StringUtils"), HealthLogger = require("utils/HealthLogger"); var SupportedEncodingsText = require("text!supported-encodings.json"), SupportedEncodings = JSON.parse(SupportedEncodingsText); /* StatusBar indicators */ var languageSelect, // this is a DropdownButton instance encodingSelect, // this is a DropdownButton instance $cursorInfo, $fileInfo, $indentType, $indentWidthLabel, $indentWidthInput, $statusOverwrite; /** Special list item for the 'set as default' gesture in language switcher dropdown */ var LANGUAGE_SET_AS_DEFAULT = {}; /** * Determine string based on count * @param {number} number Count * @param {string} singularStr Singular string * @param {string} pluralStr Plural string * @return {string} Proper string to use for count */ function _formatCountable(number, singularStr, pluralStr) { return StringUtils.format(number > 1 ? pluralStr : singularStr, number); } /** * Update file mode * @param {Editor} editor Current editor */ function _updateLanguageInfo(editor) { var doc = editor.document, lang = doc.getLanguage(); // Show the current language as button title languageSelect.$button.text(lang.getName()); } /** * Update encoding * @param {Editor} editor Current editor */ function _updateEncodingInfo(editor) { var doc = editor.document; // Show the current encoding as button title if (!doc.file._encoding) { doc.file._encoding = "UTF-8"; } encodingSelect.$button.text(doc.file._encoding); } /** * Update file information * @param {Editor} editor Current editor */ function _updateFileInfo(editor) { var lines = editor.lineCount(); $fileInfo.text(_formatCountable(lines, Strings.STATUSBAR_LINE_COUNT_SINGULAR, Strings.STATUSBAR_LINE_COUNT_PLURAL)); } /** * Update indent type and size * @param {string} fullPath Path to file in current editor */ function _updateIndentType(fullPath) { var indentWithTabs = Editor.getUseTabChar(fullPath); $indentType.text(indentWithTabs ? Strings.STATUSBAR_TAB_SIZE : Strings.STATUSBAR_SPACES); $indentType.attr("title", indentWithTabs ? Strings.STATUSBAR_INDENT_TOOLTIP_SPACES : Strings.STATUSBAR_INDENT_TOOLTIP_TABS); $indentWidthLabel.attr("title", indentWithTabs ? Strings.STATUSBAR_INDENT_SIZE_TOOLTIP_TABS : Strings.STATUSBAR_INDENT_SIZE_TOOLTIP_SPACES); } /** * Get indent size based on type * @param {string} fullPath Path to file in current editor * @return {number} Indent size */ function _getIndentSize(fullPath) { return Editor.getUseTabChar(fullPath) ? Editor.getTabSize(fullPath) : Editor.getSpaceUnits(fullPath); } /** * Update indent size * @param {string} fullPath Path to file in current editor */ function _updateIndentSize(fullPath) { var size = _getIndentSize(fullPath); $indentWidthLabel.text(size); $indentWidthInput.val(size); } /** * Toggle indent type */ function _toggleIndentType() { var current = EditorManager.getActiveEditor(), fullPath = current && current.document.file.fullPath; Editor.setUseTabChar(!Editor.getUseTabChar(fullPath), fullPath); _updateIndentType(fullPath); _updateIndentSize(fullPath); } /** * Update cursor(s)/selection(s) information * @param {Event} event (unused) * @param {Editor} editor Current editor */ function _updateCursorInfo(event, editor) { editor = editor || EditorManager.getActiveEditor(); // compute columns, account for tab size var cursor = editor.getCursorPos(true); var cursorStr = StringUtils.format(Strings.STATUSBAR_CURSOR_POSITION, cursor.line + 1, cursor.ch + 1); var sels = editor.getSelections(), selStr = ""; if (sels.length > 1) { //Send analytics data for multicursor use HealthLogger.sendAnalyticsData( "multiCursor", "usage", "multiCursor", "use" ); selStr = StringUtils.format(Strings.STATUSBAR_SELECTION_MULTIPLE, sels.length); } else if (editor.hasSelection()) { var sel = sels[0]; if (sel.start.line !== sel.end.line) { var lines = sel.end.line - sel.start.line + 1; if (sel.end.ch === 0) { lines--; // end line is exclusive if ch is 0, inclusive otherwise } selStr = _formatCountable(lines, Strings.STATUSBAR_SELECTION_LINE_SINGULAR, Strings.STATUSBAR_SELECTION_LINE_PLURAL); } else { var cols = editor.getColOffset(sel.end) - editor.getColOffset(sel.start); // end ch is exclusive always selStr = _formatCountable(cols, Strings.STATUSBAR_SELECTION_CH_SINGULAR, Strings.STATUSBAR_SELECTION_CH_PLURAL); } } $cursorInfo.text(cursorStr + selStr); } /** * Change indent size * @param {string} fullPath Path to file in current editor * @param {string} value Size entered into status bar */ function _changeIndentWidth(fullPath, value) { $indentWidthLabel.removeClass("hidden"); $indentWidthInput.addClass("hidden"); // remove all event handlers from the input field $indentWidthInput.off("blur keyup"); // restore focus to the editor MainViewManager.focusActivePane(); var valInt = parseInt(value, 10); if (Editor.getUseTabChar(fullPath)) { if (!Editor.setTabSize(valInt, fullPath)) { return; // validation failed } } else { if (!Editor.setSpaceUnits(valInt, fullPath)) { return; // validation failed } } // update indicator _updateIndentSize(fullPath); // column position may change when tab size changes _updateCursorInfo(); } /** * Update insert/overwrite label * @param {Event} event (unused) * @param {Editor} editor Current editor * @param {string} newstate New overwrite state * @param {boolean=} doNotAnimate True if state should not be animated */ function _updateOverwriteLabel(event, editor, newstate, doNotAnimate) { if ($statusOverwrite.text() === (newstate ? Strings.STATUSBAR_OVERWRITE : Strings.STATUSBAR_INSERT)) { // label already up-to-date return; } $statusOverwrite.text(newstate ? Strings.STATUSBAR_OVERWRITE : Strings.STATUSBAR_INSERT); if (!doNotAnimate) { AnimationUtils.animateUsingClass($statusOverwrite[0], "flash", 1500); } } /** * Update insert/overwrite indicator * @param {Event} event (unused) */ function _updateEditorOverwriteMode(event) { var editor = EditorManager.getActiveEditor(), newstate = !editor._codeMirror.state.overwrite; // update label with no transition _updateOverwriteLabel(event, editor, newstate, true); editor.toggleOverwrite(newstate); } /** * Initialize insert/overwrite indicator * @param {Editor} currentEditor Current editor */ function _initOverwriteMode(currentEditor) { currentEditor.toggleOverwrite($statusOverwrite.text() === Strings.STATUSBAR_OVERWRITE); $statusOverwrite.attr("title", Strings.STATUSBAR_INSOVR_TOOLTIP); } /** * Handle active editor change event * @param {Event} event (unused) * @param {Editor} current Current editor * @param {Editor} previous Previous editor */ function _onActiveEditorChange(event, current, previous) { if (previous) { previous.off(".statusbar"); previous.document.off(".statusbar"); previous.document.releaseRef(); } if (!current) { StatusBar.hideAllPanes(); } else { var fullPath = current.document.file.fullPath; StatusBar.showAllPanes(); current.on("cursorActivity.statusbar", _updateCursorInfo); current.on("optionChange.statusbar", function () { _updateIndentType(fullPath); _updateIndentSize(fullPath); }); current.on("change.statusbar", function () { // async update to keep typing speed smooth window.setTimeout(function () { _updateFileInfo(current); }, 0); }); current.on("overwriteToggle.statusbar", _updateOverwriteLabel); current.document.addRef(); current.document.on("languageChanged.statusbar", function () { _updateLanguageInfo(current); }); _updateCursorInfo(null, current); _updateLanguageInfo(current); _updateEncodingInfo(current); _updateFileInfo(current); _initOverwriteMode(current); _updateIndentType(fullPath); _updateIndentSize(fullPath); } } /** * Populate the languageSelect DropdownButton's menu with all registered Languages */ function _populateLanguageDropdown() { // Get all non-binary languages var languages = _.values(LanguageManager.getLanguages()).filter(function (language) { return !language.isBinary(); }); // sort dropdown alphabetically languages.sort(function (a, b) { return a.getName().toLowerCase().localeCompare(b.getName().toLowerCase()); }); languageSelect.items = languages; // Add option to top of menu for persisting the override languageSelect.items.unshift("---"); languageSelect.items.unshift(LANGUAGE_SET_AS_DEFAULT); } /** * Change the encoding and reload the current document. * If passed then save the preferred encoding in state. */ function _changeEncodingAndReloadDoc(document) { var promise = document.reload(); promise.done(function (text, readTimestamp) { encodingSelect.$button.text(document.file._encoding); // Store the preferred encoding in the state var projectRoot = ProjectManager.getProjectRoot(), context = { location : { scope: "user", layer: "project", layerID: projectRoot.fullPath } }; var encoding = PreferencesManager.getViewState("encoding", context); encoding[document.file.fullPath] = document.file._encoding; PreferencesManager.setViewState("encoding", encoding, context); }); promise.fail(function (error) { console.log("Error reloading contents of " + document.file.fullPath, error); }); } /** * Populate the encodingSelect DropdownButton's menu with all registered encodings */ function _populateEncodingDropdown() { encodingSelect.items = SupportedEncodings; } /** * Initialize */ function _init() { $cursorInfo = $("#status-cursor"); $fileInfo = $("#status-file"); $indentType = $("#indent-type"); $indentWidthLabel = $("#indent-width-label"); $indentWidthInput = $("#indent-width-input"); $statusOverwrite = $("#status-overwrite"); languageSelect = new DropdownButton("", [], function (item, index) { var document = EditorManager.getActiveEditor().document, defaultLang = LanguageManager.getLanguageForPath(document.file.fullPath, true); if (item === LANGUAGE_SET_AS_DEFAULT) { var label = _.escape(StringUtils.format(Strings.STATUSBAR_SET_DEFAULT_LANG, LanguageManager.getCompoundFileExtension(document.file.fullPath))); return { html: label, enabled: document.getLanguage() !== defaultLang }; } var html = _.escape(item.getName()); // Show indicators for currently selected & default languages for the current file if (item === defaultLang) { html += " " + Strings.STATUSBAR_DEFAULT_LANG + ""; } if (item === document.getLanguage()) { html = "" + html; } return html; }); languageSelect.dropdownExtraClasses = "dropdown-status-bar"; languageSelect.$button.addClass("btn-status-bar"); $("#status-language").append(languageSelect.$button); languageSelect.$button.attr("title", Strings.STATUSBAR_LANG_TOOLTIP); encodingSelect = new DropdownButton("", [], function (item, index) { var document = EditorManager.getActiveEditor().document; var html = _.escape(item); // Show indicators for currently selected & default languages for the current file if (item === "UTF-8") { html += " " + Strings.STATUSBAR_DEFAULT_LANG + ""; } if (item === document.file._encoding) { html = "" + html; } return html; }); encodingSelect.dropdownExtraClasses = "dropdown-status-bar"; encodingSelect.$button.addClass("btn-status-bar"); $("#status-encoding").append(encodingSelect.$button); encodingSelect.$button.attr("title", Strings.STATUSBAR_ENCODING_TOOLTIP); // indentation event handlers $indentType.on("click", _toggleIndentType); $indentWidthLabel .on("click", function () { // update the input value before displaying var fullPath = EditorManager.getActiveEditor().document.file.fullPath; $indentWidthInput.val(_getIndentSize(fullPath)); $indentWidthLabel.addClass("hidden"); $indentWidthInput.removeClass("hidden"); $indentWidthInput.focus(); $indentWidthInput .on("blur", function () { _changeIndentWidth(fullPath, $indentWidthInput.val()); }) .on("keyup", function (event) { if (event.keyCode === KeyEvent.DOM_VK_RETURN) { $indentWidthInput.blur(); } else if (event.keyCode === KeyEvent.DOM_VK_ESCAPE) { _changeIndentWidth(fullPath, false); } }); }); $indentWidthInput.focus(function () { $indentWidthInput.select(); }); // Language select change handler languageSelect.on("select", function (e, lang) { var document = EditorManager.getActiveEditor().document, fullPath = document.file.fullPath; var fileType = (document.file instanceof InMemoryFile) ? "newFile" : "existingFile", filelanguageName = lang ? lang._name : ""; HealthLogger.sendAnalyticsData( HealthLogger.commonStrings.USAGE + HealthLogger.commonStrings.LANGUAGE_CHANGE + filelanguageName + fileType, HealthLogger.commonStrings.USAGE, HealthLogger.commonStrings.LANGUAGE_CHANGE, filelanguageName.toLowerCase(), fileType ); if (lang === LANGUAGE_SET_AS_DEFAULT) { // Set file's current language in preferences as a file extension override (only enabled if not default already) var fileExtensionMap = PreferencesManager.get("language.fileExtensions"); fileExtensionMap[LanguageManager.getCompoundFileExtension(fullPath)] = document.getLanguage().getId(); PreferencesManager.set("language.fileExtensions", fileExtensionMap); } else { // Set selected language as a path override for just this one file (not persisted) var defaultLang = LanguageManager.getLanguageForPath(fullPath, true); // if default language selected, pass null to clear the override LanguageManager.setLanguageOverrideForPath(fullPath, lang === defaultLang ? null : lang); } }); // Encoding select change handler encodingSelect.on("select", function (e, encoding) { var document = EditorManager.getActiveEditor().document, originalPath = document.file.fullPath, originalEncoding = document.file._encoding; document.file._encoding = encoding; if (!(document.file instanceof InMemoryFile) && document.isDirty) { CommandManager.execute(Commands.FILE_SAVE_AS, {doc: document}).done(function () { var doc = DocumentManager.getCurrentDocument(); if (originalPath === doc.file.fullPath) { _changeEncodingAndReloadDoc(doc); } else { document.file._encoding = originalEncoding; } }).fail(function () { document.file._encoding = originalEncoding; }); } else if (document.file instanceof InMemoryFile) { encodingSelect.$button.text(encoding); } else if (!document.isDirty) { _changeEncodingAndReloadDoc(document); } }); $statusOverwrite.on("click", _updateEditorOverwriteMode); } // Initialize: status bar focused listener EditorManager.on("activeEditorChange", _onActiveEditorChange); function _checkFileExistance(filePath, index, encoding) { var deferred = new $.Deferred(), fileEntry = FileSystem.getFileForPath(filePath); fileEntry.exists(function (err, exists) { if (!err && exists) { deferred.resolve(); } else { delete encoding[filePath]; deferred.reject(); } }); return deferred.promise(); } ProjectManager.on("projectOpen", function () { var projectRoot = ProjectManager.getProjectRoot(), context = { location : { scope: "user", layer: "project", layerID: projectRoot.fullPath } }; var encoding = PreferencesManager.getViewState("encoding", context); if (!encoding) { encoding = {}; PreferencesManager.setViewState("encoding", encoding, context); } Async.doSequentially(Object.keys(encoding), function (filePath, index) { return _checkFileExistance(filePath, index, encoding); }, false) .always(function () { PreferencesManager.setViewState("encoding", encoding, context); }); }); AppInit.htmlReady(_init); AppInit.appReady(function () { // Populate language switcher with all languages after startup; update it later if this set changes _populateLanguageDropdown(); _populateEncodingDropdown(); LanguageManager.on("languageAdded languageModified", _populateLanguageDropdown); _onActiveEditorChange(null, EditorManager.getActiveEditor(), null); StatusBar.show(); }); }); ================================================ FILE: src/editor/ImageViewer.js ================================================ /* * Copyright (c) 2013 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ define(function (require, exports, module) { "use strict"; var DocumentManager = require("document/DocumentManager"), ImageViewTemplate = require("text!htmlContent/image-view.html"), ProjectManager = require("project/ProjectManager"), LanguageManager = require("language/LanguageManager"), MainViewFactory = require("view/MainViewFactory"), Strings = require("strings"), StringUtils = require("utils/StringUtils"), FileSystem = require("filesystem/FileSystem"), FileUtils = require("file/FileUtils"), _ = require("thirdparty/lodash"), Mustache = require("thirdparty/mustache/mustache"); var _viewers = {}; /** * ImageView objects are constructed when an image is opened * @see {@link Pane} for more information about where ImageViews are rendered * * @constructor * @param {!File} file - The image file object to render * @param {!jQuery} container - The container to render the image view in */ function ImageView(file, $container) { this.file = file; this.$el = $(Mustache.render(ImageViewTemplate, {fullPath: file.encodedPath || 'file:///' + FileUtils.encodeFilePath(file.fullPath), now: new Date().valueOf()})); $container.append(this.$el); this._naturalWidth = 0; this._naturalHeight = 0; this._scale = 100; // 100% this._scaleDivInfo = null; // coordinates of hidden scale sticker this.relPath = ProjectManager.makeProjectRelativeIfPossible(this.file.fullPath); this.$imagePath = this.$el.find(".image-path"); this.$imagePreview = this.$el.find(".image-preview"); this.$imageData = this.$el.find(".image-data"); this.$image = this.$el.find(".image"); this.$imageTip = this.$el.find(".image-tip"); this.$imageGuides = this.$el.find(".image-guide"); this.$imageScale = this.$el.find(".image-scale"); this.$x_value = this.$el.find(".x-value"); this.$y_value = this.$el.find(".y-value"); this.$horzGuide = this.$el.find(".horz-guide"); this.$vertGuide = this.$el.find(".vert-guide"); this.$imagePath.text(this.relPath).attr("title", this.relPath); this.$imagePreview.on("load", _.bind(this._onImageLoaded, this)); _viewers[file.fullPath] = this; } /** * DocumentManger.fileNameChange handler - when an image is renamed, we must * update the view * * @param {jQuery.Event} e - event * @param {!string} oldPath - the name of the file that's changing changing * @param {!string} newPath - the name of the file that's changing changing * @private */ ImageView.prototype._onFilenameChange = function (e, oldPath, newPath) { /* * File objects are already updated when the event is triggered * so we just need to see if the file has the same path as our image */ if (this.file.fullPath === newPath) { this.relPath = ProjectManager.makeProjectRelativeIfPossible(newPath); this.$imagePath.text(this.relPath).attr("title", this.relPath); } }; /** * .on("load") handler - updates content of the image view * initializes computed values * installs event handlers * @param {Event} e - event * @private */ ImageView.prototype._onImageLoaded = function (e) { // add dimensions and size this._naturalWidth = e.currentTarget.naturalWidth; this._naturalHeight = e.currentTarget.naturalHeight; var extension = FileUtils.getFileExtension(this.file.fullPath); var dimensionString = this._naturalWidth + " × " + this._naturalHeight + " " + Strings.UNIT_PIXELS; if (extension === "ico") { dimensionString += " (" + Strings.IMAGE_VIEWER_LARGEST_ICON + ")"; } // get image size var self = this; this.file.stat(function (err, stat) { if (err) { self.$imageData.html(dimensionString); } else { var sizeString = ""; if (stat.size) { sizeString = " — " + StringUtils.prettyPrintBytes(stat.size, 2); } var dimensionAndSize = dimensionString + sizeString; self.$imageData.html(dimensionAndSize) .attr("title", dimensionAndSize .replace("×", "x") .replace("—", "-")); } }); // make sure we always show the right file name DocumentManager.on("fileNameChange.ImageView", _.bind(this._onFilenameChange, this)); this.$imageTip.hide(); this.$imageGuides.hide(); this.$image.on("mousemove.ImageView", ".image-preview", _.bind(this._showImageTip, this)) .on("mouseleave.ImageView", ".image-preview", _.bind(this._hideImageTip, this)); this._updateScale(); }; /** * Update the scale element * @private */ ImageView.prototype._updateScale = function () { var currentWidth = this.$imagePreview.width(); if (currentWidth && currentWidth < this._naturalWidth) { this._scale = currentWidth / this._naturalWidth * 100; this.$imageScale.text(Math.floor(this._scale) + "%") // Keep the position of the image scale div relative to the image. .css("left", this.$imagePreview.position().left + 5) .show(); } else { // Reset everything related to the image scale sticker before hiding it. this._scale = 100; this._scaleDivInfo = null; this.$imageScale.text("").hide(); } }; /** * Show image coordinates under the mouse cursor * @param {Event} e - event * @private */ ImageView.prototype._showImageTip = function (e) { // Don't show image tip if this._scale is close to zero. // since we won't have enough room to show tip anyway. if (Math.floor(this._scale) === 0) { return; } var x = Math.round(e.offsetX * 100 / this._scale), y = Math.round(e.offsetY * 100 / this._scale), imagePos = this.$imagePreview.position(), left = e.offsetX + imagePos.left, top = e.offsetY + imagePos.top, width = this.$imagePreview.width(), height = this.$imagePreview.height(), windowWidth = $(window).width(), fourDigitImageWidth = this._naturalWidth.toString().length === 4, // @todo -- seems a bit strange that we're computing sizes // using magic numbers infoWidth1 = 112, // info div width 96px + vertical toolbar width 16px infoWidth2 = 120, // info div width 104px (for 4-digit image width) + vertical toolbar width 16px tipOffsetX = 10, // adjustment for info div left from x coordinate of cursor tipOffsetY = -54, // adjustment for info div top from y coordinate of cursor tipMinusOffsetX1 = -82, // for less than 4-digit image width tipMinusOffsetX2 = -90; // for 4-digit image width // Check whether we're getting mousemove events beyond the image boundaries due to a browser bug // or the rounding calculation above for a scaled image. For example, if an image is 120 px wide, // we should get mousemove events in the range of 0 <= x < 120, but not 120 or more. If we get // a value beyond the range, then simply handle the event as if it were a mouseleave. if (x < 0 || x >= this._naturalWidth || y < 0 || y >= this._naturalHeight) { this._hideImageTip(e); this.$imagePreview.css("cursor", "auto"); return; } this.$imagePreview.css("cursor", "none"); this._handleMouseEnterOrExitScaleSticker(left, top); // Check whether to show the image tip on the left. if ((e.pageX + infoWidth1) > windowWidth || (fourDigitImageWidth && (e.pageX + infoWidth2) > windowWidth)) { tipOffsetX = fourDigitImageWidth ? tipMinusOffsetX2 : tipMinusOffsetX1; } this.$x_value.text(x + "px"); this.$y_value.text(y + "px"); this.$imageTip.css({ left: left + tipOffsetX, top: top + tipOffsetY }).show(); this.$horzGuide.css({ left: imagePos.left, top: top, width: width - 1 }).show(); this.$vertGuide.css({ left: left, top: imagePos.top, height: height - 1 }).show(); }; /** * Hide image coordinates info tip * @param {Event} e - event * @private */ ImageView.prototype._hideImageTip = function (e) { var $target = $(e.target), targetPos = $target.position(), imagePos = this.$imagePreview.position(), right = imagePos.left + this.$imagePreview.width(), bottom = imagePos.top + this.$imagePreview.height(), x = targetPos.left + e.offsetX, y = targetPos.top + e.offsetY; // Hide image tip and guides only if the cursor is outside of the image. if (x < imagePos.left || x >= right || y < imagePos.top || y >= bottom) { this._hideGuidesAndTip(); if (this._scaleDivInfo) { this._scaleDivInfo = null; this.$imageScale.show(); } } }; /** * Hides both guides and the tip * @private */ ImageView.prototype._hideGuidesAndTip = function () { this.$imageTip.hide(); this.$imageGuides.hide(); }; /** * Check mouse entering/exiting the scale sticker. * Hide it when entering and show it again when exiting. * * @param {number} offsetX mouse offset from the left of the previewing image * @param {number} offsetY mouseoffset from the top of the previewing image * @private */ ImageView.prototype._handleMouseEnterOrExitScaleSticker = function (offsetX, offsetY) { var imagePos = this.$imagePreview.position(), scaleDivPos = this.$imageScale.position(), imgWidth = this.$imagePreview.width(), imgHeight = this.$imagePreview.height(), scaleDivLeft, scaleDivTop, scaleDivRight, scaleDivBottom; if (this._scaleDivInfo) { scaleDivLeft = this._scaleDivInfo.left; scaleDivTop = this._scaleDivInfo.top; scaleDivRight = this._scaleDivInfo.right; scaleDivBottom = this._scaleDivInfo.bottom; if ((imgWidth + imagePos.left) < scaleDivRight) { scaleDivRight = imgWidth + imagePos.left; } if ((imgHeight + imagePos.top) < scaleDivBottom) { scaleDivBottom = imgHeight + imagePos.top; } } else { scaleDivLeft = scaleDivPos.left; scaleDivTop = scaleDivPos.top; scaleDivRight = this.$imageScale.width() + scaleDivLeft; scaleDivBottom = this.$imageScale.height() + scaleDivTop; } if (this._scaleDivInfo) { // See whether the cursor is no longer inside the hidden scale div. // If so, show it again. if ((offsetX < scaleDivLeft || offsetX > scaleDivRight) || (offsetY < scaleDivTop || offsetY > scaleDivBottom)) { this._scaleDivInfo = null; this.$imageScale.show(); } } else if ((offsetX >= scaleDivLeft && offsetX <= scaleDivRight) && (offsetY >= scaleDivTop && offsetY <= scaleDivBottom)) { // Handle mouse inside image scale div. // But hide it only if the pixel under mouse is also in the image. if (offsetX < (imagePos.left + imgWidth) && offsetY < (imagePos.top + imgHeight)) { // Remember image scale div coordinates before hiding it. this._scaleDivInfo = {left: scaleDivPos.left, top: scaleDivPos.top, right: scaleDivRight, bottom: scaleDivBottom}; this.$imageScale.hide(); } } }; /** * View Interface functions */ /* * Retrieves the file object for this view * return {!File} the file object for this view */ ImageView.prototype.getFile = function () { return this.file; }; /* * Updates the layout of the view */ ImageView.prototype.updateLayout = function () { this._hideGuidesAndTip(); var $container = this.$el.parent(); var pos = $container.position(), iWidth = $container.innerWidth(), iHeight = $container.innerHeight(), oWidth = $container.outerWidth(), oHeight = $container.outerHeight(); // $view is "position:absolute" so // we have to update the height, width and position this.$el.css({top: pos.top + ((oHeight - iHeight) / 2), left: pos.left + ((oWidth - iWidth) / 2), width: iWidth, height: iHeight}); this._updateScale(); }; /* * Destroys the view */ ImageView.prototype.destroy = function () { delete _viewers[this.file.fullPath]; DocumentManager.off(".ImageView"); this.$image.off(".ImageView"); this.$el.remove(); }; /* * Refreshes the image preview with what's on disk */ ImageView.prototype.refresh = function () { var noCacheUrl = this.$imagePreview.attr("src"), now = new Date().valueOf(), index = noCacheUrl.indexOf("?"); // strip the old param off if (index > 0) { noCacheUrl = noCacheUrl.slice(0, index); } // add a new param which will force chrome to // re-read the image from disk noCacheUrl = noCacheUrl + "?ver=" + now; // Update the DOM node with the src URL this.$imagePreview.attr("src", noCacheUrl); }; /* * Creates an image view object and adds it to the specified pane * @param {!File} file - the file to create an image of * @param {!Pane} pane - the pane in which to host the view * @return {jQuery.Promise} */ function _createImageView(file, pane) { var view = pane.getViewForPath(file.fullPath); if (view) { pane.showView(view); } else { view = new ImageView(file, pane.$content); pane.addView(view, true); } return new $.Deferred().resolve().promise(); } /** * Handles file system change events so we can refresh * image viewers for the files that changed on disk due to external editors * @param {jQuery.event} event - event object * @param {?File} file - file object that changed * @param {Array.=} added If entry is a Directory, contains zero or more added children * @param {Array.=} removed If entry is a Directory, contains zero or more removed children */ function _handleFileSystemChange(event, entry, added, removed) { // this may have been called because files were added // or removed to the file system. We don't care about those if (!entry || entry.isDirectory) { return; } // Look for a viewer for the changed file var viewer = _viewers[entry.fullPath]; // viewer found, call its refresh method if (viewer) { viewer.refresh(); } } /* * Install an event listener to receive all file system change events * so we can refresh the view when changes are made to the image in an external editor */ FileSystem.on("change", _handleFileSystemChange); /* * Initialization, register our view factory */ MainViewFactory.registerViewFactory({ canOpenFile: function (fullPath) { var lang = LanguageManager.getLanguageForPath(fullPath); return (lang.getId() === "image"); }, openFile: function (file, pane) { return _createImageView(file, pane); } }); /* * This is for extensions that want to create a * view factory based on ImageViewer */ exports.ImageView = ImageView; }); ================================================ FILE: src/editor/InlineTextEditor.js ================================================ /* * Copyright (c) 2012 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ // FUTURE: Merge part (or all) of this class with MultiRangeInlineEditor define(function (require, exports, module) { "use strict"; // Load dependent modules var CodeMirror = require("thirdparty/CodeMirror/lib/codemirror"), EventDispatcher = require("utils/EventDispatcher"), DocumentManager = require("document/DocumentManager"), EditorManager = require("editor/EditorManager"), CommandManager = require("command/CommandManager"), Commands = require("command/Commands"), InlineWidget = require("editor/InlineWidget").InlineWidget, KeyEvent = require("utils/KeyEvent"); /** * Shows or hides the dirty indicator * @private */ function _showDirtyIndicator($indicatorDiv, isDirty) { // Show or hide the dirty indicator by adjusting // the width of the div. $indicatorDiv.css("width", isDirty ? 16 : 0); } /** * Respond to dirty flag change event. If the dirty flag is associated with an inline editor, * show (or hide) the dirty indicator. * @private */ function _dirtyFlagChangeHandler(event, doc) { var $dirtyIndicators = $(".inline-text-editor .dirty-indicator"), $indicator; $dirtyIndicators.each(function (index, indicator) { $indicator = $(this); if ($indicator.data("fullPath") === doc.file.fullPath) { _showDirtyIndicator($indicator, doc.isDirty); } }); } /** * @constructor * @extends {InlineWidget} */ function InlineTextEditor() { InlineWidget.call(this); this.editor = null; // We need to set this as a capture handler so CodeMirror doesn't handle Esc before we see it. this.handleKeyDown = this.handleKeyDown.bind(this); this.htmlContent.addEventListener("keydown", this.handleKeyDown, true); } InlineTextEditor.prototype = Object.create(InlineWidget.prototype); InlineTextEditor.prototype.constructor = InlineTextEditor; InlineTextEditor.prototype.parentClass = InlineWidget.prototype; InlineTextEditor.prototype.$wrapper = null; /** @type {Editor} */ InlineTextEditor.prototype.editor = null; InlineTextEditor.prototype.$editorHolder = null; InlineTextEditor.prototype.$header = null; InlineTextEditor.prototype.$filename = null; /** * Given a host editor and its inline editors, find the widest gutter and make all the others match * @param {!Editor} hostEditor Host editor containing all the inline editors to sync * @private */ function _syncGutterWidths(hostEditor) { var allHostedEditors = EditorManager.getInlineEditors(hostEditor); // add the host itself to the list too allHostedEditors.push(hostEditor); var maxWidth = 0; allHostedEditors.forEach(function (editor) { var $gutter = $(editor._codeMirror.getGutterElement()).find(".CodeMirror-linenumbers"); $gutter.css("min-width", ""); var curWidth = $gutter.width(); if (curWidth > maxWidth) { maxWidth = curWidth; } }); if (allHostedEditors.length === 1) { //There's only the host, just refresh the gutter allHostedEditors[0]._codeMirror.setOption("gutters", allHostedEditors[0]._codeMirror.getOption("gutters")); return; } maxWidth = maxWidth + "px"; allHostedEditors.forEach(function (editor) { $(editor._codeMirror.getGutterElement()).find(".CodeMirror-linenumbers").css("min-width", maxWidth); // Force CodeMirror to refresh the gutter editor._codeMirror.setOption("gutters", editor._codeMirror.getOption("gutters")); }); } /** * Called any time inline was closed, whether manually (via close()) or automatically */ InlineTextEditor.prototype.onClosed = function () { InlineTextEditor.prototype.parentClass.onClosed.apply(this, arguments); _syncGutterWidths(this.hostEditor); // Destroy the inline editor. this.setInlineContent(null); this.htmlContent.removeEventListener("keydown", this.handleKeyDown, true); }; /** * Update the inline editor's height when the number of lines change. The * base implementation of this method does nothing. */ InlineTextEditor.prototype.sizeInlineWidgetToContents = function () { // brackets_codemirror_overrides.css adds height:auto to CodeMirror // Inline editors themselves do not need to be sized, but layouts like // the one used in CSSInlineEditor do need some manual layout. }; /** * Some tasks have to wait until we've been parented into the outer editor * @param {string} the inline ID that is generated by CodeMirror after the widget that holds the inline * editor is constructed and added to the DOM */ InlineTextEditor.prototype.onAdded = function () { var self = this; InlineTextEditor.prototype.parentClass.onAdded.apply(this, arguments); if (this.editor) { this.editor.refresh(); } // Update display of inline editors when the hostEditor signals a redraw CodeMirror.on(this.info, "redraw", function () { // At the point where we get the redraw, CodeMirror might not yet have actually // re-added the widget to the DOM. This is filed as https://github.com/codemirror/CodeMirror/issues/1226. // For now, we can work around it by doing the refresh on a setTimeout(). window.setTimeout(function () { if (self.editor) { self.editor.refresh(); } }, 0); }); _syncGutterWidths(this.hostEditor); if (this.editor) { this.editor.focus(); } }; /** * @return {?Editor} If an Editor within this inline editor has focus, returns it. Otherwise returns null. */ InlineTextEditor.prototype.getFocusedEditor = function () { if (this.editor && this.editor.hasFocus()) { return this.editor; } return null; }; /** * @private * Make sure that if we want to handle Esc to cancel a multiple selection, we don't let it bubble * up to InlineWidget, which will close the edit. */ InlineTextEditor.prototype.handleKeyDown = function (e) { if (e.keyCode === KeyEvent.DOM_VK_ESCAPE && this.editor && this.editor.getSelections().length > 1) { CodeMirror.commands.singleSelection(this.editor._codeMirror); e.stopImmediatePropagation(); } }; /** * Sets the document and range to show in the inline editor, or null to destroy the current editor and leave * the content blank. * @param {Document} doc The document to show, or null to show nothing * @param {number} startLine The first line of text in `doc` to show in inline editor. Ignored if doc is null. * @param {number} endLine The last line of text in `doc` to show in inline editor. Ignored if doc is null. */ InlineTextEditor.prototype.setInlineContent = function (doc, startLine, endLine) { var self = this; // Destroy the previous editor if we had one and clear out the filename info. if (this.editor) { this.editor.off(".InlineTextEditor"); this.editor.destroy(); // remove from DOM and release ref on Document this.editor = null; this.$filename.off(".InlineTextEditor") .removeAttr("title"); this.$filename.html(""); } if (!doc) { return; } var range = { startLine: startLine, endLine: endLine }; // dirty indicator, with file path stored on it var $dirtyIndicatorDiv = $("
    ") .addClass("dirty-indicator") .html("•") .width(0); // initialize indicator as hidden $dirtyIndicatorDiv.data("fullPath", doc.file.fullPath); this.$lineNumber = $(""); // update contents of filename link this.$filename.append($dirtyIndicatorDiv) .append(doc.file.name + " : ") .append(this.$lineNumber) .attr("title", doc.file.fullPath); // clicking filename jumps to full editor view this.$filename.on("click.InlineTextEditor", function () { CommandManager.execute(Commands.FILE_OPEN, { fullPath: doc.file.fullPath }) .done(function () { EditorManager.getCurrentFullEditor().setCursorPos(startLine, 0, true); }); }); var inlineInfo = EditorManager.createInlineEditorForDocument(doc, range, this.$editorHolder.get(0)); this.editor = inlineInfo.editor; // Init line number display this._updateLineRange(inlineInfo.editor); // Always update the widget height when an inline editor completes a // display update this.editor.on("update.InlineTextEditor", function (event, editor) { self.sizeInlineWidgetToContents(); }); // Size editor to content whenever text changes (via edits here or any // other view of the doc: Editor fires "change" any time its text // changes, regardless of origin) this.editor.on("change.InlineTextEditor", function (event, editor) { if (self.hostEditor.isFullyVisible()) { self.sizeInlineWidgetToContents(); self._updateLineRange(editor); } }); // If Document's file is deleted, or Editor loses sync with Document, delegate to this._onLostContent() this.editor.on("lostContent.InlineTextEditor", function () { self._onLostContent.apply(self, arguments); }); // set dirty indicator state _showDirtyIndicator($dirtyIndicatorDiv, doc.isDirty); }; /** * Updates start line display. * @param {Editor} editor */ InlineTextEditor.prototype._updateLineRange = function (editor) { this._startLine = editor.getFirstVisibleLine(); this._endLine = editor.getLastVisibleLine(); this._lineCount = this._endLine - this._startLine; this.$lineNumber.text(this._startLine + 1); }; /** * @param {Editor} hostEditor */ InlineTextEditor.prototype.load = function (hostEditor) { InlineTextEditor.prototype.parentClass.load.apply(this, arguments); // We don't create the actual editor here--that will happen the first time // setInlineContent() is called. this.$wrapper = $("
    ").addClass("inline-text-editor").appendTo(this.$htmlContent); this.$header = $("
    ").addClass("inline-editor-header").appendTo(this.$wrapper); this.$filename = $("").addClass("filename").appendTo(this.$header); this.$editorHolder = $("
    ").addClass("inline-editor-holder").appendTo(this.$wrapper); }; /** * Called when the editor containing the inline is made visible. */ InlineTextEditor.prototype.onParentShown = function () { InlineTextEditor.prototype.parentClass.onParentShown.apply(this, arguments); // Refresh line number display and codemirror line number gutter if (this.editor) { this._updateLineRange(this.editor); this.editor.refresh(); } // We need to call this explicitly whenever the host editor is reshown this.sizeInlineWidgetToContents(); }; /** * If Document's file is deleted, or Editor loses sync with Document, just close */ InlineTextEditor.prototype._onLostContent = function () { // Note: this closes the entire inline widget if any one Editor loses sync. This seems // better than leaving it open but suddenly removing one rule from the result list. this.close(); }; // Consolidate all dirty document updates // Due to circular dependencies, not safe to call on() directly EventDispatcher.on_duringInit(DocumentManager, "dirtyFlagChange", _dirtyFlagChangeHandler); exports.InlineTextEditor = InlineTextEditor; }); ================================================ FILE: src/editor/InlineWidget.js ================================================ /* * Copyright (c) 2012 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ define(function (require, exports, module) { "use strict"; // Load dependent modules var EditorManager = require("editor/EditorManager"), EventDispatcher = require("utils/EventDispatcher"), KeyEvent = require("utils/KeyEvent"); /** * @constructor * */ function InlineWidget() { var self = this; // create the outer wrapper div this.htmlContent = window.document.createElement("div"); this.$htmlContent = $(this.htmlContent).addClass("inline-widget").attr("tabindex", "-1"); this.$htmlContent.append("
    ") .append("
    ") .append("×"); // create the close button this.$closeBtn = this.$htmlContent.find(".close"); this.$closeBtn.click(function (e) { self.close(); e.stopImmediatePropagation(); }); this.$htmlContent.on("keydown", function (e) { if (e.keyCode === KeyEvent.DOM_VK_ESCAPE) { self.close(); e.stopImmediatePropagation(); } }); } InlineWidget.prototype.htmlContent = null; InlineWidget.prototype.$htmlContent = null; InlineWidget.prototype.id = null; InlineWidget.prototype.hostEditor = null; EventDispatcher.makeEventDispatcher(InlineWidget.prototype); /** * Initial height of inline widget in pixels. Can be changed later via hostEditor.setInlineWidgetHeight() * @type {number} */ InlineWidget.prototype.height = 0; /** * Closes this inline widget and all its contained Editors * @return {$.Promise} A promise that's resolved when the widget is fully closed. */ InlineWidget.prototype.close = function () { return EditorManager.closeInlineWidget(this.hostEditor, this); // closeInlineWidget() causes our onClosed() handler to be called }; /** * @return {boolean} True if any part of the inline widget is focused */ InlineWidget.prototype.hasFocus = function () { var focusedItem = window.document.activeElement, htmlContent = this.$htmlContent[0]; return $.contains(htmlContent, focusedItem) || htmlContent === focusedItem; }; /** * Called any time inline is closed, whether manually or automatically. */ InlineWidget.prototype.onClosed = function () { this.trigger("close"); }; /** * Called once content is parented in the host editor's DOM. Useful for performing tasks like setting * focus or measuring content, which require htmlContent to be in the DOM tree. * * IMPORTANT: onAdded() MUST be overridden to call hostEditor.setInlineWidgetHeight() at least once to * set the initial height (required to animate it open). The widget will never open otherwise. */ InlineWidget.prototype.onAdded = function () { this.trigger("add"); }; /** * @param {Editor} hostEditor */ InlineWidget.prototype.load = function (hostEditor) { this.hostEditor = hostEditor; }; /** * Called when the editor containing the inline is made visible. */ InlineWidget.prototype.onParentShown = function () { // do nothing - base implementation }; /** * Called when the parent editor does a full refresh--for example, when the font size changes. */ InlineWidget.prototype.refresh = function () { // do nothing - base implementation }; exports.InlineWidget = InlineWidget; }); ================================================ FILE: src/editor/MultiRangeInlineEditor.js ================================================ /* * Copyright (c) 2012 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ // FUTURE: Merge part (or all) of this class with InlineTextEditor /** * An inline editor for displaying and editing multiple text ranges. Each range corresponds to a * contiguous set of lines in a file. * * In the current implementation, only one range is visible at a time. A list on the right side * of the editor allows the user to select which range is visible. * * This module does not dispatch any events. */ define(function (require, exports, module) { "use strict"; var _ = require("thirdparty/lodash"); // Load dependent modules var TextRange = require("document/TextRange").TextRange, InlineTextEditor = require("editor/InlineTextEditor").InlineTextEditor, EditorManager = require("editor/EditorManager"), FileUtils = require("file/FileUtils"), PreferencesManager = require("preferences/PreferencesManager"), ProjectManager = require("project/ProjectManager"), Commands = require("command/Commands"), Strings = require("strings"), CommandManager = require("command/CommandManager"); var _prevMatchCmd, _nextMatchCmd; /** * Remove trailing "px" from a style size value. * @param {!JQuery} $target Element in DOM * @param {!string} styleName Style name to query * @return {number} Style value converted from string to number, removing "px" units */ function _parseStyleSize($target, styleName) { return parseInt($target.css(styleName), 10); } /** Returns a 'context' object for getting/setting project-specific preferences */ function _getPrefsContext() { var projectRoot = ProjectManager.getProjectRoot(); // note: null during unit tests! return { location : { scope: "user", layer: "project", layerID: projectRoot && projectRoot.fullPath } }; } /** * Stores one search result: its source file, line range, etc. plus the DOM node representing it * in the results list. * @constructor */ function SearchResultItem(rangeResult) { this.name = rangeResult.name; this.textRange = new TextRange(rangeResult.document, rangeResult.lineStart, rangeResult.lineEnd); // this.$listItem is assigned in load() } SearchResultItem.prototype.name = null; SearchResultItem.prototype.textRange = null; SearchResultItem.prototype.$listItem = null; function _updateRangeLabel(listItem, range, labelCB) { if (labelCB) { range.name = labelCB(range.textRange); } var text = _.escape(range.name) + " :" + (range.textRange.startLine + 1) + ""; listItem.html(text); listItem.attr("title", listItem.text()); } /** * @constructor * @param {Array.<{name:String,document:Document,lineStart:number,lineEnd:number}>} ranges The text * ranges to display. Results within the same file are expected to be contiguous in this array. * @param {?function(): $.Promise} messageCB Optional; returns a promise resolved with a message to * show when no matches are available. The message should be already-escaped HTML. * @param {?function(range): string} labelCB Optional; returns an updated label string for the given * range. Called when we detect that the content of a range has changed. The label is plain * text, not HTML. * @param {?function(!File, !File):number} fileComparator Optional comparison function for sorting * the results list (based on range.document.file). Defaults to FileUtils.comparePaths(). * @extends {InlineTextEditor} */ function MultiRangeInlineEditor(ranges, messageCB, labelCB, fileComparator) { InlineTextEditor.call(this); // Store the results to show in the range list. This creates TextRanges bound to the Document, // which will stay up to date automatically (but we must be sure to detach them later) this._ranges = ranges.map(function (rangeResult) { return new SearchResultItem(rangeResult); }); this._messageCB = messageCB; this._labelCB = labelCB; this._selectedRangeIndex = -1; this._collapsedFiles = {}; // Set up list sort order this._fileComparator = fileComparator || function defaultComparator(file1, file2) { return FileUtils.comparePaths(file1.fullPath, file2.fullPath); }; this._ranges.sort(function (result1, result2) { return this._fileComparator(result1.textRange.document.file, result2.textRange.document.file); }.bind(this)); } MultiRangeInlineEditor.prototype = Object.create(InlineTextEditor.prototype); MultiRangeInlineEditor.prototype.constructor = MultiRangeInlineEditor; MultiRangeInlineEditor.prototype.parentClass = InlineTextEditor.prototype; MultiRangeInlineEditor.prototype.$messageDiv = null; MultiRangeInlineEditor.prototype.$relatedContainer = null; MultiRangeInlineEditor.prototype.$related = null; MultiRangeInlineEditor.prototype.$selectedMarker = null; /** Includes all the _ranges[i].$listItem items, as well as section headers */ MultiRangeInlineEditor.prototype.$rangeList = null; /** * List of search results. Section headers are not represented in this list (they are implied before each group of * of consecutive results from the same Document). * @type {!Array.} */ MultiRangeInlineEditor.prototype._ranges = null; /** Index into this._ranges - indices do not include section headers */ MultiRangeInlineEditor.prototype._selectedRangeIndex = null; /** * Map from fullPath to true if collapsed. May not agree with preferences, in cases where multiple inline editors make * concurrent changes. * @type {!Object.} */ MultiRangeInlineEditor.prototype._collapsedFiles = null; MultiRangeInlineEditor.prototype._messageCB = null; MultiRangeInlineEditor.prototype._labelCB = null; MultiRangeInlineEditor.prototype._fileComparator = null; /** @type {!Object.} Map from fullPath to section header DOM node */ MultiRangeInlineEditor.prototype._$headers = null; /** * @private * Add a new result item
  • to the range list UI ($rangeList) and saves it in range.$listItem * @param {SearchResultItem} range The range to add. */ MultiRangeInlineEditor.prototype._createListItem = function (range) { var self = this, $rangeItem = $("
  • "); // Attach filename for unit test use $rangeItem.data("filename", range.textRange.document.file.name); $rangeItem.appendTo(this.$rangeList); _updateRangeLabel($rangeItem, range); $rangeItem.mousedown(function () { self.setSelectedIndex(self._ranges.indexOf(range)); }); range.$listItem = $rangeItem; }; /** Collapses/expands a file section in the range list UI */ MultiRangeInlineEditor.prototype._toggleSection = function (fullPath, duringInit) { var $headerItem = this._$headers[fullPath]; var $disclosureIcon = $headerItem.find(".disclosure-triangle"); var isCollapsing = $disclosureIcon.hasClass("expanded"); $disclosureIcon.toggleClass("expanded"); $headerItem.nextUntil(".section-header").toggle(!isCollapsing); // explicit visibility arg, since during load() jQ doesn't think nodes are visible // Update instance-specific state... this._collapsedFiles[fullPath] = isCollapsing; // ...AND persist as per-project view state if (!duringInit) { var setting = PreferencesManager.getViewState("inlineEditor.collapsedFiles", _getPrefsContext()) || {}; if (isCollapsing) { setting[fullPath] = true; } else { delete setting[fullPath]; } PreferencesManager.setViewState("inlineEditor.collapsedFiles", setting, _getPrefsContext()); } // Show/hide selection indicator if selection was in collapsed section this._updateSelectedMarker(false); // Changing height of rule list may change ht of overall editor this._ruleListHeightChanged(); // If user expands collapsed section and nothing selected yet, select first result in this section if (this._selectedRangeIndex === -1 && !isCollapsing && !duringInit) { var index = _.findIndex(this._ranges, function (resultItem) { return resultItem.textRange.document.file.fullPath === fullPath; }); this.setSelectedIndex(index); } }; /** Adds a file section header
  • to the range list UI ($rangeList) and adds it to the this._$headers map */ MultiRangeInlineEditor.prototype._createHeaderItem = function (doc) { var $headerItem = $("
  • " + _.escape(doc.file.name) + "
  • ") .attr("title", ProjectManager.makeProjectRelativeIfPossible(doc.file.fullPath)) .appendTo(this.$rangeList); $headerItem.click(function () { this._toggleSection(doc.file.fullPath); }.bind(this)); this._$headers[doc.file.fullPath] = $headerItem; }; /** Refresh the contents of $rangeList */ MultiRangeInlineEditor.prototype._renderList = function () { this.$rangeList.empty(); this._$headers = {}; var self = this, lastSectionDoc, numItemsInSection = 0; // After seeing all results for a given file, update its header with total # of results function finalizeSection() { if (lastSectionDoc) { self._$headers[lastSectionDoc.file.fullPath].append(" (" + numItemsInSection + ")"); if (self._collapsedFiles[lastSectionDoc.file.fullPath]) { self._toggleSection(lastSectionDoc.file.fullPath, true); } } } this._ranges.forEach(function (resultItem) { if (lastSectionDoc !== resultItem.textRange.document) { // Finalize previous section finalizeSection(); // Initialize new section lastSectionDoc = resultItem.textRange.document; numItemsInSection = 0; // Create filename header for new section this._createHeaderItem(lastSectionDoc); } numItemsInSection++; this._createListItem(resultItem); }, this); // Finalize last section finalizeSection(); }; /** * @override * @param {!Editor} hostEditor Outer Editor instance that inline editor will sit within. * */ MultiRangeInlineEditor.prototype.load = function (hostEditor) { MultiRangeInlineEditor.prototype.parentClass.load.apply(this, arguments); // Create the message area this.$messageDiv = $("
    ") .addClass("inline-editor-message"); // Prevent touch scroll events from bubbling up to the parent editor. this.$editorHolder.on("mousewheel.MultiRangeInlineEditor", function (e) { e.stopPropagation(); }); // Outer container for border-left and scrolling this.$relatedContainer = $("
    ").addClass("related-container"); // List "selection" highlight this.$selectedMarker = $("
    ").appendTo(this.$relatedContainer).addClass("selection"); // Inner container this.$related = $("
    ").appendTo(this.$relatedContainer).addClass("related"); // Range list this.$rangeList = $("
      ").appendTo(this.$related); // Determine which sections are initially collapsed (the actual collapsing happens after onAdded(), // because jQuery.hide() requires the computed value of 'display' to work properly) var toCollapse = PreferencesManager.getViewState("inlineEditor.collapsedFiles", _getPrefsContext()) || {}; Object.keys(toCollapse).forEach(function (fullPath) { this._collapsedFiles[fullPath] = true; }.bind(this)); // Render list & section headers (matching collapsed state set above) this._renderList(); if (this._ranges.length > 1) { // attach to main container this.$wrapper.before(this.$relatedContainer); } // Add TextRange listeners to update UI as text changes var self = this; this._ranges.forEach(function (range, index) { // Update list item as TextRange changes range.textRange.on("change", function () { _updateRangeLabel(range.$listItem, range); }).on("contentChange", function () { _updateRangeLabel(range.$listItem, range, self._labelCB); }); // If TextRange lost sync, remove it from the list (and close the widget if no other ranges are left) range.textRange.on("lostSync", function () { self._removeRange(range); }); }); // Initial selection is the first non-collapsed result item var indexToSelect = _.findIndex(this._ranges, function (range) { return !this._collapsedFiles[range.textRange.document.file.fullPath]; }.bind(this)); if (this._ranges.length === 1 && indexToSelect === -1) { // If no right-hand rule list shown, select the one result even if it's in a collapsed file (since no way to expand) indexToSelect = 0; } if (indexToSelect !== -1) { // select the first visible range this.setSelectedIndex(indexToSelect); } else { // force the message div to show this.setSelectedIndex(-1); } // Listen for clicks directly on us, so we can set focus back to the editor var clickHandler = this._onClick.bind(this); this.$htmlContent.on("click.MultiRangeInlineEditor", clickHandler); // Also handle mouseup in case the user drags a little bit this.$htmlContent.on("mouseup.MultiRangeInlineEditor", clickHandler); // Update the rule list navigation menu items when we gain/lose focus. this.$htmlContent .on("focusin.MultiRangeInlineEditor", this._updateCommands.bind(this)) .on("focusout.MultiRangeInlineEditor", this._updateCommands.bind(this)); }; /** * @private * Updates the enablement for the rule list navigation commands. */ MultiRangeInlineEditor.prototype._updateCommands = function () { var enabled = (this.hasFocus() && this._ranges.length > 1); _prevMatchCmd.setEnabled(enabled && this._selectedRangeIndex > 0); _nextMatchCmd.setEnabled(enabled && this._selectedRangeIndex !== -1 && this._selectedRangeIndex < this._ranges.length - 1); }; /** * @override */ MultiRangeInlineEditor.prototype.onAdded = function () { // Set the initial position of the selected marker now that we're laid out. this._updateSelectedMarker(false); // Call super MultiRangeInlineEditor.prototype.parentClass.onAdded.apply(this, arguments); // Initially size the inline widget (calls sizeInlineWidgetToContents()) this._ruleListHeightChanged(); this._updateCommands(); }; /** * Specify the range that is shown in the editor. * * @param {!number} index The index of the range to select, or -1 to deselect all. Index into this._ranges, * so section headers are not included in the sequence. * @param {boolean} force Whether to re-select the item even if we think it's already selected * (used if the range list has changed). */ MultiRangeInlineEditor.prototype.setSelectedIndex = function (index, force) { var newIndex = Math.min(Math.max(-1, index), this._ranges.length - 1), self = this; if (!force && newIndex !== -1 && newIndex === this._selectedRangeIndex) { return; } // Remove selected class(es) var $previousItem = (this._selectedRangeIndex >= 0) ? this._ranges[this._selectedRangeIndex].$listItem : null; if ($previousItem) { $previousItem.removeClass("selected"); } // Clear our listeners on the previous editor since it'll be destroyed in setInlineContent(). if (this.editor) { this.editor.off(".MultiRangeInlineEditor"); } this._selectedRangeIndex = newIndex; if (newIndex === -1) { // show the message div this.setInlineContent(null); var hasHiddenMatches = this._ranges.length > 0; if (hasHiddenMatches) { this.$messageDiv.text(Strings.INLINE_EDITOR_HIDDEN_MATCHES); } else if (this._messageCB) { this._messageCB(hasHiddenMatches).done(function (msg) { self.$messageDiv.html(msg); }); } else { this.$messageDiv.text(Strings.INLINE_EDITOR_NO_MATCHES); } this.$htmlContent.append(this.$messageDiv); this.sizeInlineWidgetToContents(); } else { this.$messageDiv.remove(); var range = this._getSelectedRange(); range.$listItem.addClass("selected"); // Add new editor this.setInlineContent(range.textRange.document, range.textRange.startLine, range.textRange.endLine); this.editor.focus(); this._updateEditorMinHeight(); this.editor.refresh(); // Ensure the cursor position is visible in the host editor as the user is arrowing around. this.editor.on("cursorActivity.MultiRangeInlineEditor", this._ensureCursorVisible.bind(this)); // ensureVisibility is set to false because we don't want to scroll the main editor when the user selects a view this.sizeInlineWidgetToContents(); this._updateSelectedMarker(true); } this._updateCommands(); }; /** * Ensures that the editor's min-height is set so it never gets shorter than the rule list. * This is necessary to make sure the editor's horizontal scrollbar stays at the bottom of the * widget. */ MultiRangeInlineEditor.prototype._updateEditorMinHeight = function () { if (!this.editor) { return; } // Set the scroller's min-height to the natural height of the rule list, so the editor // always stays at least as tall as the rule list. var ruleListNaturalHeight = this.$related.outerHeight(), headerHeight = $(".inline-editor-header", this.$htmlContent).outerHeight(); // If the widget isn't fully loaded yet, bail--we'll get called again in onAdded(). if (!ruleListNaturalHeight || !headerHeight) { return; } // We have to set this on the scroller instead of the wrapper because: // * we want the wrapper's actual height to remain "auto" // * if we set a min-height on the wrapper, the scroller's height: 100% doesn't // respect it (height: 100% doesn't seem to work properly with min-height on the parent) $(this.editor.getScrollerElement()) .css("min-height", (ruleListNaturalHeight - headerHeight) + "px"); }; /** Update inline widget height to reflect changed rule-list height */ MultiRangeInlineEditor.prototype._ruleListHeightChanged = function () { // Editor's min height depends on rule list height this._updateEditorMinHeight(); // Overall widget height may have changed too this.sizeInlineWidgetToContents(); }; MultiRangeInlineEditor.prototype._removeRange = function (range) { // If this is the last range, just close the whole widget if (this._ranges.length <= 1) { this.close(); return; // note: the dispose() that would normally happen below is covered by close() } // Now we know there is at least one other range -> found out which one this is var index = this._ranges.indexOf(range); // If the range to be removed is the selected one, first switch to another one if (index === this._selectedRangeIndex) { // If possible, select the one below, else select the one above if (index + 1 < this._ranges.length) { this.setSelectedIndex(index + 1); } else { this.setSelectedIndex(index - 1); } } // Now we can remove this range range.textRange.dispose(); this._ranges.splice(index, 1); // Re-render list & section headers this._renderList(); // Move selection highlight if deletion affected its position if (index < this._selectedRangeIndex) { this._selectedRangeIndex--; this._updateSelectedMarker(true); } if (this._ranges.length === 1) { this.$relatedContainer.remove(); // Refresh the height of the inline editor since we remove // the entire selector list. if (this.editor) { this.editor.refresh(); } } this._updateCommands(); }; /** * Adds a new range to the inline editor and selects it. The range will be inserted * immediately below the last range for the same document, or at the end of the list * if there are no other ranges for that document. * @param {string} name The label for the new range. * @param {Document} doc The document the range is in. * @param {number} lineStart The starting line of the range, 0-based, inclusive. * @param {number} lineEnd The ending line of the range, 0-based, inclusive. */ MultiRangeInlineEditor.prototype.addAndSelectRange = function (name, doc, lineStart, lineEnd) { var newRange = new SearchResultItem({ name: name, document: doc, lineStart: lineStart, lineEnd: lineEnd }), i; // Insert the new range after the last range from the same doc, or at the // end of the list. for (i = 0; i < this._ranges.length; i++) { if (this._fileComparator(this._ranges[i].textRange.document.file, doc.file) > 0) { break; } } this._ranges.splice(i, 0, newRange); // Update rule list display this._renderList(); // Ensure rule list is visible if there are now multiple results if (this._ranges.length > 1 && !this.$relatedContainer.parent().length) { this.$wrapper.before(this.$relatedContainer); } // If added rule is in a collapsed item, expand it for clarity if (this._collapsedFiles[doc.file.fullPath]) { this._toggleSection(doc.file.fullPath); } // Select new range, showing it in the editor this.setSelectedIndex(i, true); // force, since i might be same as before this._updateCommands(); }; MultiRangeInlineEditor.prototype._updateSelectedMarker = function (animate) { // If no selection or selection is in a collapsed section, just hide the marker if (this._selectedRangeIndex < 0 || this._collapsedFiles[this._getSelectedRange().textRange.document.file.fullPath]) { this.$selectedMarker.hide(); return; } var $rangeItem = this._ranges[this._selectedRangeIndex].$listItem; // scroll the selection to the rangeItem var containerHeight = this.$relatedContainer.height(), itemTop = $rangeItem.position().top, scrollTop = this.$relatedContainer.scrollTop(); this.$selectedMarker .show() .toggleClass("animate", animate) .css("top", itemTop) .height($rangeItem.outerHeight()); if (containerHeight <= 0) { return; } var paddingTop = _parseStyleSize($rangeItem.parent(), "paddingTop"); if ((itemTop - paddingTop) < scrollTop) { this.$relatedContainer.scrollTop(itemTop - paddingTop); } else { var itemBottom = itemTop + $rangeItem.height() + _parseStyleSize($rangeItem.parent(), "paddingBottom"); if (itemBottom > (scrollTop + containerHeight)) { this.$relatedContainer.scrollTop(itemBottom - containerHeight); } } }; /** * Called any time inline is closed, whether manually (via closeThisInline()) or automatically */ MultiRangeInlineEditor.prototype.onClosed = function () { // Superclass onClosed() destroys editor MultiRangeInlineEditor.prototype.parentClass.onClosed.apply(this, arguments); // de-ref all the Documents in the search results this._ranges.forEach(function (searchResult) { searchResult.textRange.dispose(); }); // Remove event handlers this.$htmlContent.off(".MultiRangeInlineEditor"); this.$editorHolder.off(".MultiRangeInlineEditor"); }; /** * Prevent clicks in the dead areas of the inlineWidget from changing the focus and insertion point in the editor. * This is done by detecting clicks in the inlineWidget that are not inside the editor or the range list and * restoring focus and the insertion point. */ MultiRangeInlineEditor.prototype._onClick = function (event) { if (!this.editor) { return; } var childEditor = this.editor, editorRoot = childEditor.getRootElement(), editorPos = $(editorRoot).offset(); function containsClick($parent) { return $parent.find(event.target).length > 0 || $parent[0] === event.target; } // Ignore clicks in editor and clicks on filename link // Check clicks on filename link in the context of the current inline widget. if (!containsClick($(editorRoot)) && !containsClick($(".filename", this.$htmlContent))) { childEditor.focus(); // Only set the cursor if the click isn't in the range list. if (!containsClick(this.$relatedContainer)) { if (event.pageY < editorPos.top) { childEditor.setCursorPos(0, 0); } else if (event.pageY > editorPos.top + $(editorRoot).height()) { var lastLine = childEditor.getLastVisibleLine(); childEditor.setCursorPos(lastLine, childEditor.document.getLine(lastLine).length); } } } }; /** * Based on the position of the cursor in the inline editor, determine whether we need to change the * vertical scroll position of the host editor to ensure that the cursor is visible. */ MultiRangeInlineEditor.prototype._ensureCursorVisible = function () { if (!this.editor) { return; } if ($.contains(this.editor.getRootElement(), window.document.activeElement)) { var hostScrollPos = this.hostEditor.getScrollPos(), cursorCoords = this.editor._codeMirror.cursorCoords(); // Vertically, we want to set the scroll position relative to the overall host editor, not // the lineSpace of the widget itself. We don't want to modify the horizontal scroll position. var scrollerTop = this.hostEditor.getVirtualScrollAreaTop(); this.hostEditor._codeMirror.scrollIntoView({ left: hostScrollPos.x, top: cursorCoords.top - scrollerTop, right: hostScrollPos.x, bottom: cursorCoords.bottom - scrollerTop }); } }; /** * Overwrite InlineTextEditor's _onLostContent to do nothing if the document's file is deleted * (deletes are handled via TextRange's lostSync). */ MultiRangeInlineEditor.prototype._onLostContent = function (event, cause) { // Ignore when the editor's content got lost due to a deleted file if (cause && cause.type === "deleted") { return; } // Else yield to the parent's implementation return MultiRangeInlineEditor.prototype.parentClass._onLostContent.apply(this, arguments); }; /** * @return {Array.} */ MultiRangeInlineEditor.prototype._getRanges = function () { return this._ranges; }; /** * @return {!SearchResultItem} */ MultiRangeInlineEditor.prototype._getSelectedRange = function () { return this._selectedRangeIndex >= 0 ? this._ranges[this._selectedRangeIndex] : null; }; /** * Move the selection up or down, skipping any collapsed groups. If selection is currently IN a * collapsed group, we expand it first so that other items in the same file are eligible. */ MultiRangeInlineEditor.prototype._selectNextPrev = function (dir) { if (this._selectedRangeIndex === -1) { return; } // Traverse up or down the list until we find an item eligible for selection var origDoc = this._ranges[this._selectedRangeIndex].textRange.document, i; for (i = this._selectedRangeIndex + dir; i >= 0 && i < this._ranges.length; i += dir) { var doc = this._ranges[i].textRange.document; // If first candidate is in same collapsed group as current selection, expand it if (doc === origDoc && this._collapsedFiles[doc.file.fullPath]) { this._toggleSection(doc.file.fullPath); } // Only consider expanded groups now if (!this._collapsedFiles[doc.file.fullPath]) { this.setSelectedIndex(i); return; } } // If we got here, we couldn't find any eligible item - so do nothing. Happens if selection is // already the first/last item, or if all remaining items above/below the selection are collapsed. }; /** * Display the next range in the range list */ MultiRangeInlineEditor.prototype._selectNextRange = function () { this._selectNextPrev(1); }; /** * Display the previous range in the range list */ MultiRangeInlineEditor.prototype._selectPreviousRange = function () { this._selectNextPrev(-1); }; /** * Sizes the inline widget height to be the maximum between the range list height and the editor height * @override */ MultiRangeInlineEditor.prototype.sizeInlineWidgetToContents = function () { // Size the code mirror editors height to the editor content MultiRangeInlineEditor.prototype.parentClass.sizeInlineWidgetToContents.call(this); // Size the widget height to the max between the editor/message content and the related ranges list var widgetHeight = Math.max(this.$related.height(), this.$header.outerHeight() + (this._selectedRangeIndex === -1 ? this.$messageDiv.outerHeight() : this.$editorHolder.height())); if (widgetHeight) { this.hostEditor.setInlineWidgetHeight(this, widgetHeight, false); } }; /** * Called when the editor containing the inline is made visible. Updates UI based on * state that might have changed while the editor was hidden. */ MultiRangeInlineEditor.prototype.onParentShown = function () { MultiRangeInlineEditor.prototype.parentClass.onParentShown.apply(this, arguments); this._updateSelectedMarker(false); }; /** * Refreshes the height of the inline editor and all child editors. * @override */ MultiRangeInlineEditor.prototype.refresh = function () { MultiRangeInlineEditor.prototype.parentClass.refresh.apply(this, arguments); this.sizeInlineWidgetToContents(); if (this.editor) { this.editor.refresh(); } }; /** * Returns the currently focused MultiRangeInlineEditor. * @return {MultiRangeInlineEditor} */ function getFocusedMultiRangeInlineEditor() { var focusedWidget = EditorManager.getFocusedInlineWidget(); if (focusedWidget instanceof MultiRangeInlineEditor) { return focusedWidget; } else { return null; } } /** * Previous Range command handler */ function _previousRange() { var focusedMultiRangeInlineEditor = getFocusedMultiRangeInlineEditor(); if (focusedMultiRangeInlineEditor) { focusedMultiRangeInlineEditor._selectPreviousRange(); } } /** * Next Range command handler */ function _nextRange() { var focusedMultiRangeInlineEditor = getFocusedMultiRangeInlineEditor(); if (focusedMultiRangeInlineEditor) { focusedMultiRangeInlineEditor._selectNextRange(); } } _prevMatchCmd = CommandManager.register(Strings.CMD_QUICK_EDIT_PREV_MATCH, Commands.QUICK_EDIT_PREV_MATCH, _previousRange); _prevMatchCmd.setEnabled(false); _nextMatchCmd = CommandManager.register(Strings.CMD_QUICK_EDIT_NEXT_MATCH, Commands.QUICK_EDIT_NEXT_MATCH, _nextRange); _nextMatchCmd.setEnabled(false); exports.MultiRangeInlineEditor = MultiRangeInlineEditor; exports.getFocusedMultiRangeInlineEditor = getFocusedMultiRangeInlineEditor; }); ================================================ FILE: src/extensibility/ExtensionManager.js ================================================ /* * Copyright (c) 2013 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /*jslint regexp: true */ /*unittests: ExtensionManager*/ /** * The ExtensionManager fetches/caches the extension registry and provides * information about the status of installed extensions. ExtensionManager raises the * following events: * - statusChange - indicates that an extension has been installed/uninstalled or * its status has otherwise changed. Second parameter is the id of the * extension. * - registryUpdate - indicates that an existing extension was synchronized * with new data from the registry. */ define(function (require, exports, module) { "use strict"; var _ = require("thirdparty/lodash"), EventDispatcher = require("utils/EventDispatcher"), Package = require("extensibility/Package"), AppInit = require("utils/AppInit"), Async = require("utils/Async"), ExtensionLoader = require("utils/ExtensionLoader"), ExtensionUtils = require("utils/ExtensionUtils"), FileSystem = require("filesystem/FileSystem"), FileUtils = require("file/FileUtils"), PreferencesManager = require("preferences/PreferencesManager"), Strings = require("strings"), StringUtils = require("utils/StringUtils"), ThemeManager = require("view/ThemeManager"); // semver.browser is an AMD-compatible module var semver = require("thirdparty/semver.browser"); /** * @private * @type {$.Deferred} Keeps track of the current registry download so that if a request is already * in progress and another request to download the registry comes in, we don't send yet another request. * This is primarily used when multiple view models need to download the registry at the same time. */ var pendingDownloadRegistry = null; /** * Extension status constants. */ var ENABLED = "enabled", DISABLED = "disabled", START_FAILED = "startFailed"; /** * Extension location constants. */ var LOCATION_DEFAULT = "default", LOCATION_DEV = "dev", LOCATION_USER = "user", LOCATION_UNKNOWN = "unknown"; /** * Extension auto-install folder. Also used for preferences key. */ var FOLDER_AUTOINSTALL = "auto-install-extensions"; /** * @private * @type {Object.} * The set of all known extensions, both from the registry and locally installed. * The keys are either "name" from package.json (for extensions that have package metadata) * or the last segment of local file paths (for installed legacy extensions * with no package metadata). The fields of each record are: * registryInfo: object containing the info for this id from the main registry (containing metadata, owner, * and versions). This will be null for legacy extensions. * installInfo: object containing the info for a locally-installed extension: * metadata: the package metadata loaded from the local package.json, or null if it's a legacy extension. * This will be different from registryInfo.metadata if there's a newer version in the registry. * path: the local path to the extension folder on disk * locationType: general type of installation; one of the LOCATION_* constants above * status: the current status, one of the status constants above */ var extensions = {}; /** * Requested changes to the installed extensions. */ var _idsToRemove = {}, _idsToUpdate = {}, _idsToDisable = {}; PreferencesManager.stateManager.definePreference(FOLDER_AUTOINSTALL, "object", undefined); PreferencesManager.definePreference("extensions.sort", "string", "publishedDate", { description: Strings.SORT_EXTENSION_METHOD }); /** * @private * Synchronizes the information between the public registry and the installed * extensions. Specifically, this makes the `owner` available in each and sets * an `updateAvailable` flag. * * @param {string} id of the extension to synchronize */ function synchronizeEntry(id) { var entry = extensions[id]; // Do nothing if we only have one set of data if (!entry || !entry.installInfo || !entry.registryInfo) { return; } entry.installInfo.owner = entry.registryInfo.owner; // Assume false entry.installInfo.updateAvailable = false; entry.registryInfo.updateAvailable = false; entry.installInfo.updateCompatible = false; entry.registryInfo.updateCompatible = false; var currentVersion = entry.installInfo.metadata ? entry.installInfo.metadata.version : null; if (currentVersion && semver.lt(currentVersion, entry.registryInfo.metadata.version)) { // Note: available update may still be incompatible; we check for this when rendering the Update button in ExtensionManagerView._renderItem() entry.registryInfo.updateAvailable = true; entry.installInfo.updateAvailable = true; // Calculate updateCompatible to check if there's an update for current version of Brackets var lastCompatibleVersionInfo = _.findLast(entry.registryInfo.versions, function (versionInfo) { return !versionInfo.brackets || semver.satisfies(brackets.metadata.apiVersion, versionInfo.brackets); }); if (lastCompatibleVersionInfo && lastCompatibleVersionInfo.version && semver.lt(currentVersion, lastCompatibleVersionInfo.version)) { entry.installInfo.updateCompatible = true; entry.registryInfo.updateCompatible = true; entry.installInfo.lastCompatibleVersion = lastCompatibleVersionInfo.version; entry.registryInfo.lastCompatibleVersion = lastCompatibleVersionInfo.version; } } exports.trigger("registryUpdate", id); } /** * @private * Verifies if an extension is a theme based on the presence of the field "theme" * in the package.json. If it is a theme, then the theme file is just loaded by the * ThemeManager * * @param {string} id of the theme extension to load */ function loadTheme(id) { var extension = extensions[id]; if (extension.installInfo && extension.installInfo.metadata && extension.installInfo.metadata.theme) { ThemeManager.loadPackage(extension.installInfo); } } /** * @private * Sets our data. For unit testing only. */ function _setExtensions(newExtensions) { exports.extensions = extensions = newExtensions; Object.keys(extensions).forEach(function (id) { synchronizeEntry(id); }); } /** * @private * Clears out our existing data. For unit testing only. */ function _reset() { exports.extensions = extensions = {}; _idsToRemove = {}; _idsToUpdate = {}; _idsToDisable = {}; } /** * Downloads the registry of Brackets extensions and stores the information in our * extension info. * * @return {$.Promise} a promise that's resolved with the registry JSON data * or rejected if the server can't be reached. */ function downloadRegistry() { if (pendingDownloadRegistry) { return pendingDownloadRegistry.promise(); } pendingDownloadRegistry = new $.Deferred(); $.ajax({ url: brackets.config.extension_registry, dataType: "json", cache: false }) .done(function (data) { exports.hasDownloadedRegistry = true; Object.keys(data).forEach(function (id) { if (!extensions[id]) { extensions[id] = {}; } extensions[id].registryInfo = data[id]; synchronizeEntry(id); }); exports.trigger("registryDownload"); pendingDownloadRegistry.resolve(); }) .fail(function () { pendingDownloadRegistry.reject(); }) .always(function () { // Make sure to clean up the pending registry so that new requests can be made. pendingDownloadRegistry = null; }); return pendingDownloadRegistry.promise(); } /** * @private * When an extension is loaded, fetches the package.json and stores the extension in our map. * @param {$.Event} e The event object * @param {string} path The local path of the loaded extension's folder. */ function _handleExtensionLoad(e, path) { function setData(metadata) { var locationType, id = metadata.name, userExtensionPath = ExtensionLoader.getUserExtensionPath(); if (path.indexOf(userExtensionPath) === 0) { locationType = LOCATION_USER; } else { var segments = path.split("/"), parent; if (segments.length > 2) { parent = segments[segments.length - 2]; } if (parent === "dev") { locationType = LOCATION_DEV; } else if (parent === "default") { locationType = LOCATION_DEFAULT; } else { locationType = LOCATION_UNKNOWN; } } if (!extensions[id]) { extensions[id] = {}; } extensions[id].installInfo = { metadata: metadata, path: path, locationType: locationType, status: (e.type === "loadFailed" ? START_FAILED : (e.type === "disabled" ? DISABLED : ENABLED)) }; synchronizeEntry(id); loadTheme(id); exports.trigger("statusChange", id); } function deduceMetadata() { var match = path.match(/\/([^\/]+)$/), name = (match && match[1]) || path, metadata = { name: name, title: name }; return metadata; } ExtensionUtils.loadMetadata(path) .done(function (metadata) { setData(metadata); }) .fail(function (disabled) { // If there's no package.json, this is a legacy extension. It was successfully loaded, // but we don't have an official ID or metadata for it, so we just create an id and // "title" for it (which is the last segment of its pathname) // and record that it's enabled. var metadata = deduceMetadata(); metadata.disabled = disabled; setData(metadata); }); } /** * Determines if the given versions[] entry is compatible with the given Brackets API version, and if not * specifies why. * @param {Object} extVersion * @param {string} apiVersion * @return {{isCompatible: boolean, requiresNewer: ?boolean, compatibleVersion: ?string}} */ function getCompatibilityInfoForVersion(extVersion, apiVersion) { var requiredVersion = (extVersion.brackets || (extVersion.engines && extVersion.engines.brackets)), result = {}; result.isCompatible = !requiredVersion || semver.satisfies(apiVersion, requiredVersion); if (result.isCompatible) { result.compatibleVersion = extVersion.version; } else { // Find out reason for incompatibility if (requiredVersion.charAt(0) === '<') { result.requiresNewer = false; } else if (requiredVersion.charAt(0) === '>') { result.requiresNewer = true; } else if (requiredVersion.charAt(0) === "~") { var compareVersion = requiredVersion.slice(1); // Need to add .0s to this style of range in order to compare (since valid version // numbers must have major/minor/patch). if (compareVersion.match(/^[0-9]+$/)) { compareVersion += ".0.0"; } else if (compareVersion.match(/^[0-9]+\.[0-9]+$/)) { compareVersion += ".0"; } result.requiresNewer = semver.lt(apiVersion, compareVersion); } } return result; } /** * Finds the newest version of the entry that is compatible with the given Brackets API version, if any. * @param {Object} entry The registry entry to check. * @param {string} apiVersion The Brackets API version to check against. * @return {{isCompatible: boolean, requiresNewer: ?boolean, compatibleVersion: ?string, isLatestVersion: boolean}} * Result contains an "isCompatible" member saying whether it's compatible. If compatible, "compatibleVersion" * specifies the newest version that is compatible and "isLatestVersion" indicates if this is the absolute * latest version of the extension or not. If !isCompatible or !isLatestVersion, "requiresNewer" says whether * the latest version is incompatible due to requiring a newer (vs. older) version of Brackets. */ function getCompatibilityInfo(entry, apiVersion) { if (!entry.versions) { var fallback = getCompatibilityInfoForVersion(entry.metadata, apiVersion); if (fallback.isCompatible) { fallback.isLatestVersion = true; } return fallback; } var i = entry.versions.length - 1, latestInfo = getCompatibilityInfoForVersion(entry.versions[i], apiVersion); if (latestInfo.isCompatible) { latestInfo.isLatestVersion = true; return latestInfo; } else { // Look at earlier versions (skipping very latest version since we already checked it) for (i--; i >= 0; i--) { var compatInfo = getCompatibilityInfoForVersion(entry.versions[i], apiVersion); if (compatInfo.isCompatible) { compatInfo.isLatestVersion = false; compatInfo.requiresNewer = latestInfo.requiresNewer; return compatInfo; } } // No version is compatible, so just return info for the latest version return latestInfo; } } /** * Given an extension id and version number, returns the URL for downloading that extension from * the repository. Does not guarantee that the extension exists at that URL. * @param {string} id The extension's name from the metadata. * @param {string} version The version to download. * @return {string} The URL to download the extension from. */ function getExtensionURL(id, version) { return StringUtils.format(brackets.config.extension_url, id, version); } /** * Removes the installed extension with the given id. * @param {string} id The id of the extension to remove. * @return {$.Promise} A promise that's resolved when the extension is removed or * rejected with an error if there's a problem with the removal. */ function remove(id) { var result = new $.Deferred(); if (extensions[id] && extensions[id].installInfo) { Package.remove(extensions[id].installInfo.path) .done(function () { extensions[id].installInfo = null; result.resolve(); exports.trigger("statusChange", id); }) .fail(function (err) { result.reject(err); }); } else { result.reject(StringUtils.format(Strings.EXTENSION_NOT_INSTALLED, id)); } return result.promise(); } /** * @private * * Disables or enables the installed extensions. * * @param {string} id The id of the extension to disable or enable. * @param {boolean} enable A boolean indicating whether to enable or disable. * @return {$.Promise} A promise that's resolved when the extension action is * completed or rejected with an error that prevents the action from completion. */ function _enableOrDisable(id, enable) { var result = new $.Deferred(), extension = extensions[id]; if (extension && extension.installInfo) { Package[(enable ? "enable" : "disable")](extension.installInfo.path) .done(function () { extension.installInfo.status = enable ? ENABLED : DISABLED; extension.installInfo.metadata.disabled = !enable; result.resolve(); exports.trigger("statusChange", id); }) .fail(function (err) { result.reject(err); }); } else { result.reject(StringUtils.format(Strings.EXTENSION_NOT_INSTALLED, id)); } return result.promise(); } /** * Disables the installed extension with the given id. * * @param {string} id The id of the extension to disable. * @return {$.Promise} A promise that's resolved when the extenion is disabled or * rejected with an error that prevented the disabling. */ function disable(id) { return _enableOrDisable(id, false); } /** * Enables the installed extension with the given id. * * @param {string} id The id of the extension to enable. * @return {$.Promise} A promise that's resolved when the extenion is enabled or * rejected with an error that prevented the enabling. */ function enable(id) { return _enableOrDisable(id, true); } /** * Updates an installed extension with the given package file. * @param {string} id of the extension * @param {string} packagePath path to the package file * @param {boolean=} keepFile Flag to keep extension package file, default=false * @return {$.Promise} A promise that's resolved when the extension is updated or * rejected with an error if there's a problem with the update. */ function update(id, packagePath, keepFile) { return Package.installUpdate(packagePath, id).done(function () { if (!keepFile) { FileSystem.getFileForPath(packagePath).unlink(); } }); } /** * Deletes any temporary files left behind by extensions that * were marked for update. */ function cleanupUpdates() { Object.keys(_idsToUpdate).forEach(function (id) { var installResult = _idsToUpdate[id], keepFile = installResult.keepFile, filename = installResult.localPath; if (filename && !keepFile) { FileSystem.getFileForPath(filename).unlink(); } }); _idsToUpdate = {}; } /** * Unmarks all extensions marked for removal. */ function unmarkAllForRemoval() { _idsToRemove = {}; } /** * Marks an extension for later removal, or unmarks an extension previously marked. * @param {string} id The id of the extension to mark for removal. * @param {boolean} mark Whether to mark or unmark it. */ function markForRemoval(id, mark) { if (mark) { _idsToRemove[id] = true; } else { delete _idsToRemove[id]; } exports.trigger("statusChange", id); } /** * Returns true if an extension is marked for removal. * @param {string} id The id of the extension to check. * @return {boolean} true if it's been marked for removal, false otherwise. */ function isMarkedForRemoval(id) { return !!(_idsToRemove[id]); } /** * Returns true if there are any extensions marked for removal. * @return {boolean} true if there are extensions to remove */ function hasExtensionsToRemove() { return Object.keys(_idsToRemove).length > 0; } /** * Marks an extension for disabling later, or unmarks an extension previously marked. * * @param {string} id The id of the extension * @param {boolean} mark Whether to mark or unmark the extension. */ function markForDisabling(id, mark) { if (mark) { _idsToDisable[id] = true; } else { delete _idsToDisable[id]; } exports.trigger("statusChange", id); } /** * Returns true if an extension is mark for disabling. * * @param {string} id The id of the extension to check. * @return {boolean} true if it's been mark for disabling, false otherwise. */ function isMarkedForDisabling(id) { return !!(_idsToDisable[id]); } /** * Returns true if there are any extensions marked for disabling. * @return {boolean} true if there are extensions to disable */ function hasExtensionsToDisable() { return Object.keys(_idsToDisable).length > 0; } /** * Unmarks all the extensions that have been marked for disabling. */ function unmarkAllForDisabling() { _idsToDisable = {}; } /** * If a downloaded package appears to be an update, mark the extension for update. * If an extension was previously marked for removal, marking for update will * turn off the removal mark. * @param {Object} installationResult info about the install provided by the Package.download function */ function updateFromDownload(installationResult) { if (installationResult.keepFile === undefined) { installationResult.keepFile = false; } var installationStatus = installationResult.installationStatus; if (installationStatus === Package.InstallationStatuses.ALREADY_INSTALLED || installationStatus === Package.InstallationStatuses.NEEDS_UPDATE || installationStatus === Package.InstallationStatuses.SAME_VERSION || installationStatus === Package.InstallationStatuses.OLDER_VERSION) { var id = installationResult.name; delete _idsToRemove[id]; _idsToUpdate[id] = installationResult; exports.trigger("statusChange", id); } } /** * Removes the mark for an extension to be updated on restart. Also deletes the * downloaded package file. * @param {string} id The id of the extension for which the update is being removed */ function removeUpdate(id) { var installationResult = _idsToUpdate[id]; if (!installationResult) { return; } if (installationResult.localPath && !installationResult.keepFile) { FileSystem.getFileForPath(installationResult.localPath).unlink(); } delete _idsToUpdate[id]; exports.trigger("statusChange", id); } /** * Returns true if an extension is marked for update. * @param {string} id The id of the extension to check. * @return {boolean} true if it's been marked for update, false otherwise. */ function isMarkedForUpdate(id) { return !!(_idsToUpdate[id]); } /** * Returns true if there are any extensions marked for update. * @return {boolean} true if there are extensions to update */ function hasExtensionsToUpdate() { return Object.keys(_idsToUpdate).length > 0; } /** * Removes extensions previously marked for removal. * @return {$.Promise} A promise that's resolved when all extensions are removed, or rejected * if one or more extensions can't be removed. When rejected, the argument will be an * array of error objects, each of which contains an "item" property with the id of the * failed extension and an "error" property with the actual error. */ function removeMarkedExtensions() { return Async.doInParallel_aggregateErrors( Object.keys(_idsToRemove), function (id) { return remove(id); } ); } /** * Disables extensions marked for disabling. * * If the return promise is rejected, the argument will contain an array of objects. Each * element is an object identifying the extension failed with "item" property set to the * extension id which has failed to be disabled and "error" property set to the error. * * @return {$.Promise} A promise that's resolved when all extensions marked for disabling are * disabled or rejected if one or more extensions can't be disabled. */ function disableMarkedExtensions() { return Async.doInParallel_aggregateErrors( Object.keys(_idsToDisable), function (id) { return disable(id); } ); } /** * Updates extensions previously marked for update. * @return {$.Promise} A promise that's resolved when all extensions are updated, or rejected * if one or more extensions can't be updated. When rejected, the argument will be an * array of error objects, each of which contains an "item" property with the id of the * failed extension and an "error" property with the actual error. */ function updateExtensions() { return Async.doInParallel_aggregateErrors( Object.keys(_idsToUpdate), function (id) { var installationResult = _idsToUpdate[id]; return update(installationResult.name, installationResult.localPath, installationResult.keepFile); } ); } /** * Gets an array of extensions that are currently installed and can be updated to a new version * @return {Array.<{id: string, installVersion: string, registryVersion: string}>} * where id = extensionId * installVersion = currently installed version of extension * registryVersion = latest version compatible with current Brackets */ function getAvailableUpdates() { var result = []; Object.keys(extensions).forEach(function (extensionId) { var extensionInfo = extensions[extensionId]; // skip extensions that are not installed or are not in the registry if (!extensionInfo.installInfo || !extensionInfo.registryInfo) { return; } if (extensionInfo.registryInfo.updateCompatible) { result.push({ id: extensionId, installVersion: extensionInfo.installInfo.metadata.version, registryVersion: extensionInfo.registryInfo.lastCompatibleVersion }); } }); return result; } /** * Takes the array returned from getAvailableUpdates() as an input and removes those entries * that are no longer current - when currently installed version of an extension * is equal or newer than registryVersion returned by getAvailableUpdates(). * This function is designed to work without the necessity to download extension registry * @param {Array.<{id: string, installVersion: string, registryVersion: string}>} updates * previous output of getAvailableUpdates() * @return {Array.<{id: string, installVersion: string, registryVersion: string}>} * filtered input as function description */ function cleanAvailableUpdates(updates) { return updates.reduce(function (arr, updateInfo) { var extDefinition = extensions[updateInfo.id]; if (!extDefinition || !extDefinition.installInfo) { // extension has been uninstalled in the meantime return arr; } var installedVersion = extDefinition.installInfo.metadata.version; if (semver.lt(installedVersion, updateInfo.registryVersion)) { arr.push(updateInfo); } return arr; }, []); } /** * @private * Find valid extensions in specified path * @param {string} dirPath Directory with extensions * @param {Object} autoExtensions Object that maps names of previously auto-installed * extensions {string} to installed version {string}. * @return {$.Promise} Promise that resolves with arrays for extensions to update and install */ function _getAutoInstallFiles(dirPath, autoExtensions) { var zipFiles = [], installZips = [], updateZips = [], deferred = new $.Deferred(); FileSystem.getDirectoryForPath(dirPath).getContents(function (err, contents) { if (!err) { zipFiles = contents.filter(function (dirItem) { return (dirItem.isFile && FileUtils.getFileExtension(dirItem.fullPath) === "zip"); }); } // Parse zip files and separate new installs vs. updates Async.doInParallel_aggregateErrors(zipFiles, function (file) { var zipFilePromise = new $.Deferred(); // Call validate() so that we open the local zip file and parse the // package.json. We need the name to detect if this zip will be a // new install or an update. Package.validate(file.fullPath, { requirePackageJSON: true }).done(function (info) { if (info.errors.length) { zipFilePromise.reject(Package.formatError(info.errors)); return; } var extensionInfo, installedVersion, zipArray, existingItem, extensionName = info.metadata.name, autoExtVersion = autoExtensions[extensionName]; // Verify extension has not already been auto-installed/updated if (autoExtVersion && semver.lte(info.metadata.version, autoExtVersion)) { // Have already auto installed/updated version >= version of this extension zipFilePromise.reject(); return; } // Verify extension has not already been installed/updated by some other means extensionInfo = extensions[extensionName]; installedVersion = extensionInfo && extensionInfo.installInfo && extensionInfo.installInfo.metadata.version; if (installedVersion && semver.lte(info.metadata.version, installedVersion)) { // Have already manually installed/updated version >= version of this extension zipFilePromise.reject(); return; } // Update appropriate zip array. There could be multiple zip files for an // extension, so make sure only the latest is stored zipArray = (installedVersion) ? updateZips : installZips; zipArray.some(function (zip) { if (zip.info.metadata.name === extensionName) { existingItem = zip; return true; } return false; }); if (existingItem) { if (semver.lt(existingItem.info.metadata.version, info.metadata.version)) { existingItem.file = file; existingItem.info = info; } } else { zipArray.push({ file: file, info: info }); } zipFilePromise.resolve(); }).fail(function (err) { zipFilePromise.reject(Package.formatError(err)); }); return zipFilePromise.promise(); }).fail(function (errorArray) { // Async.doInParallel() fails if some are successful, so write errors // to console and always resolve errorArray.forEach(function (errorObj) { // If we rejected without an error argument, it means it was no problem // (e.g. same version of extension is already installed) if (errorObj.error) { if (errorObj.error.forEach) { console.error("Errors for", errorObj.item); errorObj.error.forEach(function (error) { console.error(Package.formatError(error)); }); } else { console.error("Error for", errorObj.item, errorObj); } } }); }).always(function () { deferred.resolve({ installZips: installZips, updateZips: updateZips }); }); }); return deferred.promise(); } /** * @private * Auto-install extensions bundled with installer * @return {$.Promise} Promise that resolves when finished */ function _autoInstallExtensions() { var dirPath = FileUtils.getDirectoryPath(FileUtils.getNativeBracketsDirectoryPath()) + FOLDER_AUTOINSTALL + "/", autoExtensions = PreferencesManager.getViewState(FOLDER_AUTOINSTALL) || {}, deferred = new $.Deferred(); _getAutoInstallFiles(dirPath, autoExtensions).done(function (result) { var installPromise = Async.doSequentially(result.installZips, function (zip) { autoExtensions[zip.info.metadata.name] = zip.info.metadata.version; return Package.installFromPath(zip.file.fullPath); }); var updatePromise = installPromise.always(function () { return Async.doSequentially(result.updateZips, function (zip) { autoExtensions[zip.info.metadata.name] = zip.info.metadata.version; return Package.installUpdate(zip.file.fullPath); }); }); // Always resolve the outer promise updatePromise.always(function () { // Keep track of auto-installed extensions so we only install an extension once PreferencesManager.setViewState(FOLDER_AUTOINSTALL, autoExtensions); deferred.resolve(); }); }); return deferred.promise(); } AppInit.appReady(function () { Package._getNodeConnectionDeferred().done(function () { _autoInstallExtensions(); }); }); // Listen to extension load and loadFailed events ExtensionLoader .on("load", _handleExtensionLoad) .on("loadFailed", _handleExtensionLoad) .on("disabled", _handleExtensionLoad); EventDispatcher.makeEventDispatcher(exports); // Public exports exports.downloadRegistry = downloadRegistry; exports.getCompatibilityInfo = getCompatibilityInfo; exports.getExtensionURL = getExtensionURL; exports.remove = remove; exports.update = update; exports.disable = disable; exports.enable = enable; exports.extensions = extensions; exports.cleanupUpdates = cleanupUpdates; exports.markForRemoval = markForRemoval; exports.isMarkedForRemoval = isMarkedForRemoval; exports.unmarkAllForRemoval = unmarkAllForRemoval; exports.hasExtensionsToRemove = hasExtensionsToRemove; exports.markForDisabling = markForDisabling; exports.isMarkedForDisabling = isMarkedForDisabling; exports.unmarkAllForDisabling = unmarkAllForDisabling; exports.hasExtensionsToDisable = hasExtensionsToDisable; exports.updateFromDownload = updateFromDownload; exports.removeUpdate = removeUpdate; exports.isMarkedForUpdate = isMarkedForUpdate; exports.hasExtensionsToUpdate = hasExtensionsToUpdate; exports.removeMarkedExtensions = removeMarkedExtensions; exports.disableMarkedExtensions = disableMarkedExtensions; exports.updateExtensions = updateExtensions; exports.getAvailableUpdates = getAvailableUpdates; exports.cleanAvailableUpdates = cleanAvailableUpdates; exports.hasDownloadedRegistry = false; exports.ENABLED = ENABLED; exports.DISABLED = DISABLED; exports.START_FAILED = START_FAILED; exports.LOCATION_DEFAULT = LOCATION_DEFAULT; exports.LOCATION_DEV = LOCATION_DEV; exports.LOCATION_USER = LOCATION_USER; exports.LOCATION_UNKNOWN = LOCATION_UNKNOWN; // For unit testing only exports._getAutoInstallFiles = _getAutoInstallFiles; exports._reset = _reset; exports._setExtensions = _setExtensions; }); ================================================ FILE: src/extensibility/ExtensionManagerDialog.js ================================================ /* * Copyright (c) 2013 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ define(function (require, exports, module) { "use strict"; var _ = require("thirdparty/lodash"), Mustache = require("thirdparty/mustache/mustache"), Dialogs = require("widgets/Dialogs"), DefaultDialogs = require("widgets/DefaultDialogs"), FileSystem = require("filesystem/FileSystem"), FileUtils = require("file/FileUtils"), Package = require("extensibility/Package"), Strings = require("strings"), StringUtils = require("utils/StringUtils"), Commands = require("command/Commands"), CommandManager = require("command/CommandManager"), InstallExtensionDialog = require("extensibility/InstallExtensionDialog"), AppInit = require("utils/AppInit"), Async = require("utils/Async"), KeyEvent = require("utils/KeyEvent"), ExtensionManager = require("extensibility/ExtensionManager"), ExtensionManagerView = require("extensibility/ExtensionManagerView").ExtensionManagerView, ExtensionManagerViewModel = require("extensibility/ExtensionManagerViewModel"), PreferencesManager = require("preferences/PreferencesManager"); var dialogTemplate = require("text!htmlContent/extension-manager-dialog.html"); // bootstrap tabs component require("widgets/bootstrap-tab"); var _activeTabIndex; function _stopEvent(event) { event.stopPropagation(); event.preventDefault(); } /** * @private * Triggers changes requested by the dialog UI. */ function _performChanges() { // If an extension was removed or updated, prompt the user to quit Brackets. var hasRemovedExtensions = ExtensionManager.hasExtensionsToRemove(), hasUpdatedExtensions = ExtensionManager.hasExtensionsToUpdate(), hasDisabledExtensions = ExtensionManager.hasExtensionsToDisable(); if (!hasRemovedExtensions && !hasUpdatedExtensions && !hasDisabledExtensions) { return; } var buttonLabel = Strings.CHANGE_AND_RELOAD; if (hasRemovedExtensions && !hasUpdatedExtensions && !hasDisabledExtensions) { buttonLabel = Strings.REMOVE_AND_RELOAD; } else if (hasUpdatedExtensions && !hasRemovedExtensions && !hasDisabledExtensions) { buttonLabel = Strings.UPDATE_AND_RELOAD; } else if (hasDisabledExtensions && !hasRemovedExtensions && !hasUpdatedExtensions) { buttonLabel = Strings.DISABLE_AND_RELOAD; } var dlg = Dialogs.showModalDialog( DefaultDialogs.DIALOG_ID_CHANGE_EXTENSIONS, Strings.CHANGE_AND_RELOAD_TITLE, Strings.CHANGE_AND_RELOAD_MESSAGE, [ { className : Dialogs.DIALOG_BTN_CLASS_NORMAL, id : Dialogs.DIALOG_BTN_CANCEL, text : Strings.CANCEL }, { className : Dialogs.DIALOG_BTN_CLASS_PRIMARY, id : Dialogs.DIALOG_BTN_OK, text : buttonLabel } ], false ), $dlg = dlg.getElement(); $dlg.one("buttonClick", function (e, buttonId) { if (buttonId === Dialogs.DIALOG_BTN_OK) { // Disable the dialog buttons so the user can't dismiss it, // and show a message indicating that we're doing the updates, // in case it takes a long time. $dlg.find(".dialog-button").prop("disabled", true); $dlg.find(".close").hide(); $dlg.find(".dialog-message") .text(Strings.PROCESSING_EXTENSIONS) .append(""); var removeExtensionsPromise, updateExtensionsPromise, disableExtensionsPromise, removeErrors, updateErrors, disableErrors; removeExtensionsPromise = ExtensionManager.removeMarkedExtensions() .fail(function (errorArray) { removeErrors = errorArray; }); updateExtensionsPromise = ExtensionManager.updateExtensions() .fail(function (errorArray) { updateErrors = errorArray; }); disableExtensionsPromise = ExtensionManager.disableMarkedExtensions() .fail(function (errorArray) { disableErrors = errorArray; }); Async.waitForAll([removeExtensionsPromise, updateExtensionsPromise, disableExtensionsPromise], true) .always(function () { dlg.close(); }) .done(function () { CommandManager.execute(Commands.APP_RELOAD); }) .fail(function () { var ids = [], dialogs = []; function nextDialog() { var dialog = dialogs.shift(); if (dialog) { Dialogs.showModalDialog(dialog.dialog, dialog.title, dialog.message) .done(nextDialog); } else { // Even in case of error condition, we still have to reload CommandManager.execute(Commands.APP_RELOAD); } } if (removeErrors) { removeErrors.forEach(function (errorObj) { ids.push(errorObj.item); }); dialogs.push({ dialog: DefaultDialogs.DIALOG_ID_ERROR, title: Strings.EXTENSION_MANAGER_REMOVE, message: StringUtils.format(Strings.EXTENSION_MANAGER_REMOVE_ERROR, ids.join(", ")) }); } if (updateErrors) { // This error case should be very uncommon. // Just let the user know that we couldn't update // this extension and log the errors to the console. ids.length = 0; updateErrors.forEach(function (errorObj) { ids.push(errorObj.item); if (errorObj.error && errorObj.error.forEach) { console.error("Errors for", errorObj.item); errorObj.error.forEach(function (error) { console.error(Package.formatError(error)); }); } else { console.error("Error for", errorObj.item, errorObj); } }); dialogs.push({ dialog: DefaultDialogs.DIALOG_ID_ERROR, title: Strings.EXTENSION_MANAGER_UPDATE, message: StringUtils.format(Strings.EXTENSION_MANAGER_UPDATE_ERROR, ids.join(", ")) }); } if (disableErrors) { ids.length = 0; disableErrors.forEach(function (errorObj) { ids.push(errorObj.item); }); dialogs.push({ dialog: DefaultDialogs.DIALOG_ID_ERROR, title: Strings.EXTENSION_MANAGER_DISABLE, message: StringUtils.format(Strings.EXTENSION_MANAGER_DISABLE_ERROR, ids.join(", ")) }); } nextDialog(); }); } else { dlg.close(); ExtensionManager.cleanupUpdates(); ExtensionManager.unmarkAllForRemoval(); ExtensionManager.unmarkAllForDisabling(); } }); } /** * @private * Install extensions from the local file system using the install dialog. * @return {$.Promise} */ function _installUsingDragAndDrop() { var installZips = [], updateZips = [], deferred = new $.Deferred(), validatePromise; brackets.app.getDroppedFiles(function (err, paths) { if (err) { // Only possible error is invalid params, silently ignore console.error(err); deferred.resolve(); return; } // Parse zip files and separate new installs vs. updates validatePromise = Async.doInParallel_aggregateErrors(paths, function (path) { var result = new $.Deferred(); FileSystem.resolve(path, function (err, file) { var extension = FileUtils.getFileExtension(path), isZip = file.isFile && (extension === "zip"), errStr; if (err) { errStr = FileUtils.getFileErrorString(err); } else if (!isZip) { errStr = Strings.INVALID_ZIP_FILE; } if (errStr) { result.reject(errStr); return; } // Call validate() so that we open the local zip file and parse the // package.json. We need the name to detect if this zip will be a // new install or an update. Package.validate(path, { requirePackageJSON: true }).done(function (info) { if (info.errors.length) { result.reject(info.errors.map(Package.formatError).join(" ")); return; } var extensionName = info.metadata.name, extensionInfo = ExtensionManager.extensions[extensionName], isUpdate = extensionInfo && !!extensionInfo.installInfo; if (isUpdate) { updateZips.push(file); } else { installZips.push(file); } result.resolve(); }).fail(function (err) { result.reject(Package.formatError(err)); }); }); return result.promise(); }); validatePromise.done(function () { var installPromise = Async.doSequentially(installZips, function (file) { return InstallExtensionDialog.installUsingDialog(file); }); var updatePromise = installPromise.then(function () { return Async.doSequentially(updateZips, function (file) { return InstallExtensionDialog.updateUsingDialog(file).done(function (result) { ExtensionManager.updateFromDownload(result); }); }); }); // InstallExtensionDialog displays it's own errors, always // resolve the outer promise updatePromise.always(deferred.resolve); }).fail(function (errorArray) { deferred.reject(errorArray); }); }); return deferred.promise(); } /** * @private * Show a dialog that allows the user to browse and manage extensions. */ function _showDialog() { var dialog, $dlg, views = [], $search, $searchClear, $modalDlg, context = { Strings: Strings, showRegistry: !!brackets.config.extension_registry }, models = []; // Load registry only if the registry URL exists if (context.showRegistry) { models.push(new ExtensionManagerViewModel.RegistryViewModel()); models.push(new ExtensionManagerViewModel.ThemesViewModel()); } models.push(new ExtensionManagerViewModel.InstalledViewModel()); models.push(new ExtensionManagerViewModel.DefaultViewModel()); function updateSearchDisabled() { var model = models[_activeTabIndex], searchDisabled = ($search.val() === "") && (!model.filterSet || model.filterSet.length === 0); $search.prop("disabled", searchDisabled); $searchClear.prop("disabled", searchDisabled); return searchDisabled; } function clearSearch() { $search.val(""); views.forEach(function (view, index) { view.filter(""); $modalDlg.scrollTop(0); }); if (!updateSearchDisabled()) { $search.focus(); } } // Open the dialog dialog = Dialogs.showModalDialogUsingTemplate(Mustache.render(dialogTemplate, context)); // On dialog close: clean up listeners & models, and commit changes dialog.done(function () { $(window.document).off(".extensionManager"); models.forEach(function (model) { model.dispose(); }); _performChanges(); }); // Create the view. $dlg = dialog.getElement(); $search = $(".search", $dlg); $searchClear = $(".search-clear", $dlg); $modalDlg = $(".modal-body", $dlg); function setActiveTab($tab) { if (models[_activeTabIndex]) { models[_activeTabIndex].scrollPos = $modalDlg.scrollTop(); } $tab.tab("show"); if (models[_activeTabIndex]) { $modalDlg.scrollTop(models[_activeTabIndex].scrollPos || 0); clearSearch(); if (_activeTabIndex === 2) { $(".ext-sort-group").hide(); } else { $(".ext-sort-group").show(); } } } // Dialog tabs $dlg.find(".nav-tabs a") .on("click", function (event) { setActiveTab($(this)); }); // Navigate through tabs via Ctrl-(Shift)-Tab // (focus may be on document.body if text in extension listing clicked - see #9511) $(window.document).on("keyup.extensionManager", function (event) { if (event.keyCode === KeyEvent.DOM_VK_TAB && event.ctrlKey) { var $tabs = $(".nav-tabs a", $dlg), tabIndex = _activeTabIndex; if (event.shiftKey) { tabIndex--; } else { tabIndex++; } tabIndex %= $tabs.length; setActiveTab($tabs.eq(tabIndex)); } }); // Update & hide/show the notification overlay on a tab's icon, based on its model's notifyCount function updateNotificationIcon(index) { var model = models[index], $notificationIcon = $dlg.find(".nav-tabs li").eq(index).find(".notification"); if (model.notifyCount) { $notificationIcon.text(model.notifyCount); $notificationIcon.show(); } else { $notificationIcon.hide(); } } // Initialize models and create a view for each model var modelInitPromise = Async.doInParallel(models, function (model, index) { var view = new ExtensionManagerView(), promise = view.initialize(model), lastNotifyCount; promise.always(function () { views[index] = view; lastNotifyCount = model.notifyCount; updateNotificationIcon(index); }); model.on("change", function () { if (lastNotifyCount !== model.notifyCount) { lastNotifyCount = model.notifyCount; updateNotificationIcon(index); } }); return promise; }, true); modelInitPromise.always(function () { $(".spinner", $dlg).remove(); views.forEach(function (view) { view.$el.appendTo($modalDlg); }); // Update search UI before new tab is shown $("a[data-toggle='tab']", $dlg).each(function (index, tabElement) { $(tabElement).on("show", function (event) { _activeTabIndex = index; // Focus the search input if (!updateSearchDisabled()) { $dlg.find(".search").focus(); } }); }); // Filter the views when the user types in the search field. var searchTimeoutID; $dlg.on("input", ".search", function (e) { clearTimeout(searchTimeoutID); var query = $(this).val(); searchTimeoutID = setTimeout(function () { views[_activeTabIndex].filter(query); $modalDlg.scrollTop(0); }, 200); }).on("click", ".search-clear", clearSearch); // Sort the extension list based on the current selected sorting criteria $dlg.on("change", ".sort-extensions", function (e) { var sortBy = $(this).val(); PreferencesManager.set("extensions.sort", sortBy); models.forEach(function (model, index) { if (index <= 1) { model._setSortedExtensionList(ExtensionManager.extensions, index === 1); views[index].filter($(".search").val()); } }); }); // Disable the search field when there are no items in the model models.forEach(function (model, index) { model.on("change", function () { if (_activeTabIndex === index) { updateSearchDisabled(); } }); }); var $activeTab = $dlg.find(".nav-tabs li.active a"); if ($activeTab.length) { // If there's already a tab selected, show it $activeTab.parent().removeClass("active"); // workaround for bootstrap-tab $activeTab.tab("show"); } else if ($("#toolbar-extension-manager").hasClass('updatesAvailable')) { // Open dialog to Installed tab if extension updates are available $dlg.find(".nav-tabs a.installed").tab("show"); } else { // Otherwise show the first tab $dlg.find(".nav-tabs a:first").tab("show"); } // If activeTab was explicitly selected by user, // then check for the selection // Or if there was an update available since activeTab.length would be 0, // then check for updatesAvailable class in toolbar-extension-manager if (($activeTab.length && $activeTab.hasClass("installed")) || (!$activeTab.length && $("#toolbar-extension-manager").hasClass('updatesAvailable'))) { $(".ext-sort-group").hide(); } else { $(".ext-sort-group").show(); } }); // Handle the 'Install from URL' button. $(".extension-manager-dialog .install-from-url") .click(function () { InstallExtensionDialog.showDialog().done(ExtensionManager.updateFromDownload); }); // Handle the drag/drop zone var $dropzone = $("#install-drop-zone"), $dropmask = $("#install-drop-zone-mask"); $dropzone .on("dragover", function (event) { _stopEvent(event); if (!event.originalEvent.dataTransfer.files) { return; } var items = event.originalEvent.dataTransfer.items, isValidDrop = false; isValidDrop = _.every(items, function (item) { if (item.kind === "file") { var entry = item.webkitGetAsEntry(), extension = FileUtils.getFileExtension(entry.fullPath); return entry.isFile && extension === "zip"; } return false; }); if (isValidDrop) { // Set an absolute width to stabilize the button size $dropzone.width($dropzone.width()); // Show drop styling and message $dropzone.removeClass("drag"); $dropzone.addClass("drop"); } else { event.originalEvent.dataTransfer.dropEffect = "none"; } }) .on("drop", _stopEvent); $dropmask .on("dragover", function (event) { _stopEvent(event); event.originalEvent.dataTransfer.dropEffect = "copy"; }) .on("dragleave", function () { $dropzone.removeClass("drop"); $dropzone.addClass("drag"); }) .on("drop", function (event) { _stopEvent(event); if (event.originalEvent.dataTransfer.files) { // Attempt install _installUsingDragAndDrop().fail(function (errorArray) { var message = Strings.INSTALL_EXTENSION_DROP_ERROR; message += "
        "; errorArray.forEach(function (info) { message += "
      • "; message += StringUtils.breakableUrl(info.item); message += ": " + info.error + "
      • "; }); message += "
      "; Dialogs.showModalDialog( DefaultDialogs.DIALOG_ID_ERROR, Strings.EXTENSION_MANAGER_TITLE, message ); }).always(function () { $dropzone.removeClass("validating"); $dropzone.addClass("drag"); }); // While installing, show validating message $dropzone.removeClass("drop"); $dropzone.addClass("validating"); } }); return new $.Deferred().resolve(dialog).promise(); } CommandManager.register(Strings.CMD_EXTENSION_MANAGER, Commands.FILE_EXTENSION_MANAGER, _showDialog); AppInit.appReady(function () { $("#toolbar-extension-manager").click(_showDialog); }); // Unit tests exports._performChanges = _performChanges; }); ================================================ FILE: src/extensibility/ExtensionManagerView.js ================================================ /* * Copyright (c) 2013 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /*unittests: ExtensionManager*/ define(function (require, exports, module) { "use strict"; var Strings = require("strings"), EventDispatcher = require("utils/EventDispatcher"), StringUtils = require("utils/StringUtils"), ExtensionManager = require("extensibility/ExtensionManager"), registry_utils = require("extensibility/registry_utils"), InstallExtensionDialog = require("extensibility/InstallExtensionDialog"), LocalizationUtils = require("utils/LocalizationUtils"), LanguageManager = require("language/LanguageManager"), Mustache = require("thirdparty/mustache/mustache"), PathUtils = require("thirdparty/path-utils/path-utils"), itemTemplate = require("text!htmlContent/extension-manager-view-item.html"), PreferencesManager = require("preferences/PreferencesManager"); /** * Create a detached link element, so that we can use it later to extract url details like 'protocol' */ var _tmpLink = window.document.createElement('a'); /** * Creates a view enabling the user to install and manage extensions. Must be initialized * with initialize(). When the view is closed, dispose() must be called. * @constructor */ function ExtensionManagerView() { } EventDispatcher.makeEventDispatcher(ExtensionManagerView.prototype); /** * Initializes the view to show a set of extensions. * @param {ExtensionManagerViewModel} model Model object containing extension data to view * @return {$.Promise} a promise that's resolved once the view has been initialized. Never * rejected. */ ExtensionManagerView.prototype.initialize = function (model) { var self = this, result = new $.Deferred(); this.model = model; this._itemTemplate = Mustache.compile(itemTemplate); this._itemViews = {}; this.$el = $("
      "); this._$emptyMessage = $("
      ") .appendTo(this.$el); this._$infoMessage = $("
      ") .appendTo(this.$el).html(this.model.infoMessage); this._$table = $("").appendTo(this.$el); $(".sort-extensions").val(PreferencesManager.get("extensions.sort")); this.model.initialize().done(function () { self._setupEventHandlers(); }).always(function () { self._render(); result.resolve(); }); return result.promise(); }; /** * @type {jQueryObject} * The root of the view's DOM tree. */ ExtensionManagerView.prototype.$el = null; /** * @type {Model} * The view's model. Handles sorting and filtering of items in the view. */ ExtensionManagerView.prototype.model = null; /** * @type {jQueryObject} * Element showing a message when there are no extensions. */ ExtensionManagerView.prototype._$emptyMessage = null; /** * @private * @type {jQueryObject} * The root of the table inside the view. */ ExtensionManagerView.prototype._$table = null; /** * @private * @type {function} The compiled template we use for rendering items in the extension list. */ ExtensionManagerView.prototype._itemTemplate = null; /** * @private * @type {Object.} * The individual views for each item, keyed by the extension ID. */ ExtensionManagerView.prototype._itemViews = null; /** * Toggles between truncated and full length extension descriptions * @param {string} id The id of the extension clicked * @param {JQueryElement} $element The DOM element of the extension clicked * @param {boolean} showFull true if full length description should be shown, false for shortened version. */ ExtensionManagerView.prototype._toggleDescription = function (id, $element, showFull) { var description, linkTitle, info = this.model._getEntry(id); // Toggle between appropriate descriptions and link title, // depending on if extension is installed or not if (showFull) { description = info.metadata.description; linkTitle = Strings.VIEW_TRUNCATED_DESCRIPTION; } else { description = info.metadata.shortdescription; linkTitle = Strings.VIEW_COMPLETE_DESCRIPTION; } $element.data("toggle-desc", showFull ? "trunc-desc" : "expand-desc") .attr("title", linkTitle) .prev(".ext-full-description").text(description); }; /** * @private * Attaches our event handlers. We wait to do this until we've fully fetched the extension list. */ ExtensionManagerView.prototype._setupEventHandlers = function () { var self = this; // Listen for model data and filter changes. this.model .on("filter", function () { self._render(); }) .on("change", function (e, id) { var extensions = self.model.extensions, $oldItem = self._itemViews[id]; self._updateMessage(); if (self.model.filterSet.indexOf(id) === -1) { // This extension is not in the filter set. Remove it from the view if we // were rendering it previously. if ($oldItem) { $oldItem.remove(); delete self._itemViews[id]; } } else { // Render the item, replacing the old item if we had previously rendered it. var $newItem = self._renderItem(extensions[id], self.model._getEntry(id)); if ($oldItem) { $oldItem.replaceWith($newItem); self._itemViews[id] = $newItem; } } }); // UI event handlers this.$el .on("click", "a", function (e) { var $target = $(e.target); if ($target.hasClass("undo-remove")) { ExtensionManager.markForRemoval($target.attr("data-extension-id"), false); } else if ($target.hasClass("remove")) { ExtensionManager.markForRemoval($target.attr("data-extension-id"), true); } else if ($target.hasClass("undo-update")) { ExtensionManager.removeUpdate($target.attr("data-extension-id")); } else if ($target.hasClass("undo-disable")) { ExtensionManager.markForDisabling($target.attr("data-extension-id"), false); } else if ($target.data("toggle-desc") === "expand-desc") { this._toggleDescription($target.attr("data-extension-id"), $target, true); } else if ($target.data("toggle-desc") === "trunc-desc") { this._toggleDescription($target.attr("data-extension-id"), $target, false); } }.bind(this)) .on("click", "button.install", function (e) { self._installUsingDialog($(e.target).attr("data-extension-id")); }) .on("click", "button.update", function (e) { self._installUsingDialog($(e.target).attr("data-extension-id"), true); }) .on("click", "button.remove", function (e) { ExtensionManager.markForRemoval($(e.target).attr("data-extension-id"), true); }) .on("click", "button.disable", function (e) { ExtensionManager.markForDisabling($(e.target).attr("data-extension-id"), true); }) .on("click", "button.enable", function (e) { ExtensionManager.enable($(e.target).attr("data-extension-id")); }); }; /** * @private * Renders the view for a single extension entry. * @param {Object} entry The extension entry to render. * @param {Object} info The extension's metadata. * @return {jQueryObject} The rendered node as a jQuery object. */ ExtensionManagerView.prototype._renderItem = function (entry, info) { // Create a Mustache context object containing the entry data and our helper functions. // Start with the basic info from the given entry, either the installation info or the // registry info depending on what we're listing. var context = $.extend({}, info); // Normally we would merge the strings into the context we're passing into the template, // but since we're instantiating the template for every item, it seems wrong to take the hit // of copying all the strings into the context, so we just make it a subfield. context.Strings = Strings; // Calculate various bools, since Mustache doesn't let you use expressions and interprets // arrays as iteration contexts. context.isInstalled = !!entry.installInfo; context.failedToStart = (entry.installInfo && entry.installInfo.status === ExtensionManager.START_FAILED); context.disabled = (entry.installInfo && entry.installInfo.status === ExtensionManager.DISABLED); context.hasVersionInfo = !!info.versions; if (entry.registryInfo) { var latestVerCompatInfo = ExtensionManager.getCompatibilityInfo(entry.registryInfo, brackets.metadata.apiVersion); context.isCompatible = latestVerCompatInfo.isCompatible; context.requiresNewer = latestVerCompatInfo.requiresNewer; context.isCompatibleLatest = latestVerCompatInfo.isLatestVersion; if (!context.isCompatibleLatest) { var installWarningBase = context.requiresNewer ? Strings.EXTENSION_LATEST_INCOMPATIBLE_NEWER : Strings.EXTENSION_LATEST_INCOMPATIBLE_OLDER; context.installWarning = StringUtils.format(installWarningBase, entry.registryInfo.versions[entry.registryInfo.versions.length - 1].version, latestVerCompatInfo.compatibleVersion); } context.downloadCount = entry.registryInfo.totalDownloads; } else { // We should only get here when viewing the Installed tab and some extensions don't exist in the registry // (or registry is offline). These flags *should* always be ignored in that scenario, but just in case... context.isCompatible = context.isCompatibleLatest = true; } // Check if extension metadata contains localized content. var lang = brackets.getLocale(), shortLang = lang.split("-")[0]; if (info.metadata["package-i18n"]) { [shortLang, lang].forEach(function (locale) { if (info.metadata["package-i18n"].hasOwnProperty(locale)) { // only overlay specific properties with the localized values ["title", "description", "homepage", "keywords"].forEach(function (prop) { if (info.metadata["package-i18n"][locale].hasOwnProperty(prop)) { info.metadata[prop] = info.metadata["package-i18n"][locale][prop]; } }); } }); } if (info.metadata.description !== undefined) { info.metadata.shortdescription = StringUtils.truncate(info.metadata.description, 200); } context.isMarkedForRemoval = ExtensionManager.isMarkedForRemoval(info.metadata.name); context.isMarkedForDisabling = ExtensionManager.isMarkedForDisabling(info.metadata.name); context.isMarkedForUpdate = ExtensionManager.isMarkedForUpdate(info.metadata.name); var hasPendingAction = context.isMarkedForDisabling || context.isMarkedForRemoval || context.isMarkedForUpdate; context.showInstallButton = (this.model.source === this.model.SOURCE_REGISTRY || this.model.source === this.model.SOURCE_THEMES) && !context.updateAvailable; context.showUpdateButton = context.updateAvailable && !context.isMarkedForUpdate && !context.isMarkedForRemoval; context.allowInstall = context.isCompatible && !context.isInstalled; if (Array.isArray(info.metadata.i18n) && info.metadata.i18n.length > 0) { context.translated = true; context.translatedLangs = info.metadata.i18n.map(function (value) { if (value === "root") { value = "en"; } return { name: LocalizationUtils.getLocalizedLabel(value), locale: value }; }) .sort(function (lang1, lang2) { // List users language first var locales = [lang1.locale, lang2.locale], userLangIndex = locales.indexOf(lang); if (userLangIndex > -1) { return userLangIndex; } userLangIndex = locales.indexOf(shortLang); if (userLangIndex > -1) { return userLangIndex; } return lang1.name.localeCompare(lang2.name); }) .map(function (value) { return value.name; }) .join(", "); context.translatedLangs = StringUtils.format(Strings.EXTENSION_TRANSLATED_LANGS, context.translatedLangs); // If the selected language is System Default, match both the short (2-char) language code // and the long one var translatedIntoUserLang = (brackets.isLocaleDefault() && info.metadata.i18n.indexOf(shortLang) > -1) || info.metadata.i18n.indexOf(lang) > -1; context.extensionTranslated = StringUtils.format( translatedIntoUserLang ? Strings.EXTENSION_TRANSLATED_USER_LANG : Strings.EXTENSION_TRANSLATED_GENERAL, info.metadata.i18n.length ); } var isInstalledInUserFolder = (entry.installInfo && entry.installInfo.locationType === ExtensionManager.LOCATION_USER); context.allowRemove = isInstalledInUserFolder; context.allowUpdate = context.showUpdateButton && context.isCompatible && context.updateCompatible && isInstalledInUserFolder; if (!context.allowUpdate) { context.updateNotAllowedReason = isInstalledInUserFolder ? Strings.CANT_UPDATE : Strings.CANT_UPDATE_DEV; } context.removalAllowed = this.model.source === "installed" && !context.failedToStart && !hasPendingAction; var isDefaultOrInstalled = this.model.source === "default" || this.model.source === "installed"; var isDefaultAndTheme = this.model.source === "default" && context.metadata.theme; context.disablingAllowed = isDefaultOrInstalled && !isDefaultAndTheme && !context.disabled && !hasPendingAction; context.enablingAllowed = isDefaultOrInstalled && !isDefaultAndTheme && context.disabled && !hasPendingAction; // Copy over helper functions that we share with the registry app. ["lastVersionDate", "authorInfo"].forEach(function (helper) { context[helper] = registry_utils[helper]; }); // Do some extra validation on homepage url to make sure we don't end up executing local binary if (context.metadata.homepage) { var parsed = PathUtils.parseUrl(context.metadata.homepage); // We can't rely on path-utils because of known problems with protocol identification // Falling back to Browsers protocol identification mechanism _tmpLink.href = context.metadata.homepage; // Check if the homepage refers to a local resource if (_tmpLink.protocol === "file:") { var language = LanguageManager.getLanguageForExtension(parsed.filenameExtension.replace(/^\./, '')); // If identified language for the local resource is binary, don't list it if (language && language.isBinary()) { delete context.metadata.homepage; } } } return $(this._itemTemplate(context)); }; /** * @private * Display an optional message (hiding the extension list if displayed) * @return {boolean} Returns true if a message is displayed */ ExtensionManagerView.prototype._updateMessage = function () { if (this.model.message) { this._$emptyMessage.css("display", "block"); this._$emptyMessage.html(this.model.message); this._$infoMessage.css("display", "none"); this._$table.css("display", "none"); return true; } else { this._$emptyMessage.css("display", "none"); this._$infoMessage.css("display", this.model.infoMessage ? "block" : "none"); this._$table.css("display", ""); return false; } }; /** * @private * Renders the extension entry table based on the model's current filter set. Will create * new items for entries that haven't yet been rendered, but will not re-render existing items. */ ExtensionManagerView.prototype._render = function () { var self = this; this._$table.empty(); this._updateMessage(); this.model.filterSet.forEach(function (id) { var $item = self._itemViews[id]; if (!$item) { $item = self._renderItem(self.model.extensions[id], self.model._getEntry(id)); self._itemViews[id] = $item; } $item.appendTo(self._$table); }); this.trigger("render"); }; /** * @private * Install the extension with the given ID using the install dialog. * @param {string} id ID of the extension to install. */ ExtensionManagerView.prototype._installUsingDialog = function (id, _isUpdate) { var entry = this.model.extensions[id]; if (entry && entry.registryInfo) { var compatInfo = ExtensionManager.getCompatibilityInfo(entry.registryInfo, brackets.metadata.apiVersion), url = ExtensionManager.getExtensionURL(id, compatInfo.compatibleVersion); // TODO: this should set .done on the returned promise if (_isUpdate) { InstallExtensionDialog.updateUsingDialog(url).done(ExtensionManager.updateFromDownload); } else { InstallExtensionDialog.installUsingDialog(url); } } }; /** * Filters the contents of the view. * @param {string} query The query to filter by. */ ExtensionManagerView.prototype.filter = function (query) { this.model.filter(query); }; exports.ExtensionManagerView = ExtensionManagerView; }); ================================================ FILE: src/extensibility/ExtensionManagerViewModel.js ================================================ /* * Copyright (c) 2013 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /*unittests: ExtensionManager*/ define(function (require, exports, module) { "use strict"; var _ = require("thirdparty/lodash"); var ExtensionManager = require("extensibility/ExtensionManager"), registry_utils = require("extensibility/registry_utils"), EventDispatcher = require("utils/EventDispatcher"), Strings = require("strings"), PreferencesManager = require("preferences/PreferencesManager"); /** * @private * @type {Array} * A list of fields to search when trying to search for a query string in an object. Each field is * represented as an array of keys to recurse downward through the object. We store this here to avoid * doing it for each search call. */ var _searchFields = [["metadata", "name"], ["metadata", "title"], ["metadata", "description"], ["metadata", "author", "name"], ["metadata", "keywords"], ["owner"]]; /** * The base model for the ExtensionManagerView. Keeps track of the extensions that are currently visible * and manages sorting/filtering them. Must be disposed with dispose() when done. * Events: * - change - triggered when the data for a given extension changes. Second parameter is the extension id. * - filter - triggered whenever the filtered set changes (including on initialize). * * @constructor */ function ExtensionManagerViewModel() { this._handleStatusChange = this._handleStatusChange.bind(this); // Listen for extension status changes. ExtensionManager .on("statusChange." + this.source, this._handleStatusChange) .on("registryUpdate." + this.source, this._handleStatusChange); } EventDispatcher.makeEventDispatcher(ExtensionManagerViewModel.prototype); /** * @type {string} * Constant indicating that this model/view should initialize from the main extension registry. */ ExtensionManagerViewModel.prototype.SOURCE_REGISTRY = "registry"; /** * @type {string} * Constant indicating that this model/view should initialize from the main extension registry with only themes. */ ExtensionManagerViewModel.prototype.SOURCE_THEMES = "themes"; /** * @type {string} * Constant indicating that this model/view should initialize from the list of locally installed extensions. */ ExtensionManagerViewModel.prototype.SOURCE_INSTALLED = "installed"; /** * @type {string} * Constant indicating that this model/view should initialize from the list of default bundled extensions. */ ExtensionManagerViewModel.prototype.SOURCE_DEFAULT = "default"; /** * @type {Object} * The current set of extensions managed by this model. Same as ExtensionManager.extensions. */ ExtensionManagerViewModel.prototype.extensions = null; /** * @type {string} * The current source for the model; one of the SOURCE_* keys above. */ ExtensionManagerViewModel.prototype.source = null; /** * @type {Array.} * The list of IDs of items matching the current query and sorted with the current sort. */ ExtensionManagerViewModel.prototype.filterSet = null; /** * @type {Object} * The list of all ids from the extension list, sorted with the current sort. */ ExtensionManagerViewModel.prototype.sortedFullSet = null; /** * @private * @type {string} * The last query we filtered by. Used to optimize future searches. */ ExtensionManagerViewModel.prototype._lastQuery = null; /** * @type {string} * Info message to display to the user when listing extensions */ ExtensionManagerViewModel.prototype.infoMessage = null; /** * @type {string} * An optional message to display to the user */ ExtensionManagerViewModel.prototype.message = null; /** * @type {number} * Number to show in tab's notification icon. No icon shown if 0. */ ExtensionManagerViewModel.prototype.notifyCount = 0; /** * @private {$.Promise} * Internal use only to track when initialization fails, see usage in _updateMessage. */ ExtensionManagerViewModel.prototype._initializeFromSourcePromise = null; /** * Unregisters listeners when we're done. */ ExtensionManagerViewModel.prototype.dispose = function () { ExtensionManager.off("." + this.source); }; /** * @private * Sets up the initial filtered set based on the sorted full set. */ ExtensionManagerViewModel.prototype._setInitialFilter = function () { // Initial filtered list is the same as the sorted list. this.filterSet = _.clone(this.sortedFullSet); this.trigger("filter"); }; /** * @private * Re-sorts the current full set based on the source we're viewing. * The base implementation does nothing. */ ExtensionManagerViewModel.prototype._sortFullSet = function () { }; /** * Initializes the model from the source. */ ExtensionManagerViewModel.prototype.initialize = function () { var self = this; this._initializeFromSourcePromise = this._initializeFromSource().always(function () { self._updateMessage(); }); return this._initializeFromSourcePromise; }; /** * @private * Updates the initial set and filter as necessary when the status of an extension changes, * and notifies listeners of the change. * @param {$.Event} e The jQuery event object. * @param {string} id The id of the extension whose status changed. */ ExtensionManagerViewModel.prototype._handleStatusChange = function (e, id) { this.trigger("change", id); }; /** * @private * Searches for the given query in the current extension list and updates the filter set, * dispatching a filter event. * @param {string} query The string to search for. * @param {boolean} force If true, always filter starting with the full set, not the last * query's filter. */ ExtensionManagerViewModel.prototype.filter = function (query, force) { var self = this, initialList; if (!force && this._lastQuery && query.indexOf(this._lastQuery) === 0) { // This is the old query with some new letters added, so we know we can just // search in the current filter set. (This is true even if query has spaces). initialList = this.filterSet; } else { // This is a new query, so start with the full list. initialList = this.sortedFullSet; } var keywords = query.toLowerCase().split(/\s+/); // Takes 'extensionList' and returns a version filtered to only those that match 'keyword' function filterForKeyword(extensionList, word) { var filteredList = []; extensionList.forEach(function (id) { var entry = self._getEntry(id); if (entry && self._entryMatchesQuery(entry, word)) { filteredList.push(id); } }); return filteredList; } // "AND" the keywords together: successively filter down the result set by each keyword in turn var i, currentList = initialList; for (i = 0; i < keywords.length; i++) { currentList = filterForKeyword(currentList, keywords[i]); } this._lastQuery = query; this.filterSet = currentList; this._updateMessage(); this.trigger("filter"); }; /** * @private * Updates an optional message displayed to the user along with the extension list. */ ExtensionManagerViewModel.prototype._updateMessage = function () { if (this._initializeFromSourcePromise && this._initializeFromSourcePromise.state() === "rejected") { this.message = Strings.EXTENSION_MANAGER_ERROR_LOAD; } else if (this.filterSet && this.filterSet.length === 0) { this.message = this.sortedFullSet && this.sortedFullSet.length ? Strings.NO_EXTENSION_MATCHES : Strings.NO_EXTENSIONS; } else { this.message = null; } }; /** * @private * This is to be overridden by subclasses to provide the metadata for the extension * with the provided `id`. * * @param {string} id of the extension * @return {Object?} extension metadata or null if there's no matching extension */ ExtensionManagerViewModel.prototype._getEntry = function (id) { return null; }; /** * @private * Tests if the given entry matches the query. * @param {Object} entry The extension entry to test. * @param {string} query The query to match against. * @return {boolean} Whether the query matches. */ ExtensionManagerViewModel.prototype._entryMatchesQuery = function (entry, query) { return query === "" || _searchFields.some(function (fieldSpec) { var i, cur = entry; for (i = 0; i < fieldSpec.length; i++) { // Recurse downward through the specified fields to the leaf value. cur = cur[fieldSpec[i]]; if (!cur) { return false; } } // If the leaf value is an array (like keywords), search each item, otherwise // just search in the string. if (Array.isArray(cur)) { return cur.some(function (keyword) { return keyword.toLowerCase().indexOf(query) !== -1; }); } else if (fieldSpec[fieldSpec.length - 1] === "owner") { // Special handling: ignore the authentication source when querying, // since it's not useful to search on var components = cur.split(":"); if (components[1].toLowerCase().indexOf(query) !== -1) { return true; } } else if (cur.toLowerCase().indexOf(query) !== -1) { return true; } }); }; ExtensionManagerViewModel.prototype._setSortedExtensionList = function (extensions, isTheme) { this.filterSet = this.sortedFullSet = registry_utils.sortRegistry(extensions, "registryInfo", PreferencesManager.get("extensions.sort")) .filter(function (entry) { if (!isTheme) { return entry.registryInfo && !entry.registryInfo.metadata.theme; } else { return entry.registryInfo && entry.registryInfo.metadata.theme; } }) .map(function (entry) { return entry.registryInfo.metadata.name; }); }; /** * The model for the ExtensionManagerView that is responsible for handling registry-based extensions. * This extends ExtensionManagerViewModel. * Must be disposed with dispose() when done. * * Events: * - change - triggered when the data for a given extension changes. Second parameter is the extension id. * - filter - triggered whenever the filtered set changes (including on initialize). * * @constructor */ function RegistryViewModel() { ExtensionManagerViewModel.call(this); this.infoMessage = Strings.REGISTRY_SANITY_CHECK_WARNING; } RegistryViewModel.prototype = Object.create(ExtensionManagerViewModel.prototype); RegistryViewModel.prototype.constructor = RegistryViewModel; /** * @type {string} * RegistryViewModels always have a source of SOURCE_REGISTRY. */ RegistryViewModel.prototype.source = ExtensionManagerViewModel.prototype.SOURCE_REGISTRY; /** * Initializes the model from the remote extension registry. * @return {$.Promise} a promise that's resolved with the registry JSON data * or rejected if the server can't be reached. */ RegistryViewModel.prototype._initializeFromSource = function () { var self = this; return ExtensionManager.downloadRegistry() .done(function () { self.extensions = ExtensionManager.extensions; // Sort the registry by last published date and store the sorted list of IDs. self._setSortedExtensionList(ExtensionManager.extensions, false); self._setInitialFilter(); }) .fail(function () { self.extensions = []; self.sortedFullSet = []; self.filterSet = []; }); }; /** * @private * Finds the extension metadata by id. If there is no extension matching the given id, * this returns `null`. * @param {string} id of the extension * @return {Object?} extension metadata or null if there's no matching extension */ RegistryViewModel.prototype._getEntry = function (id) { var entry = this.extensions[id]; if (entry) { return entry.registryInfo; } return entry; }; /** * The model for the ExtensionManagerView that is responsible for handling previously-installed extensions. * This extends ExtensionManagerViewModel. * Must be disposed with dispose() when done. * * Events: * - change - triggered when the data for a given extension changes. Second parameter is the extension id. * - filter - triggered whenever the filtered set changes (including on initialize). * * @constructor */ function InstalledViewModel() { ExtensionManagerViewModel.call(this); // when registry is downloaded, sort extensions again - those with updates will be before others var self = this; ExtensionManager.on("registryDownload." + this.source, function () { self._sortFullSet(); self._setInitialFilter(); }); } InstalledViewModel.prototype = Object.create(ExtensionManagerViewModel.prototype); InstalledViewModel.prototype.constructor = InstalledViewModel; /** * @type {string} * InstalledViewModels always have a source of SOURCE_INSTALLED. */ InstalledViewModel.prototype.source = ExtensionManagerViewModel.prototype.SOURCE_INSTALLED; /** * Initializes the model from the set of locally installed extensions, sorted * alphabetically by id (or name of the extension folder for legacy extensions). * @return {$.Promise} a promise that's resolved when we're done initializing. */ InstalledViewModel.prototype._initializeFromSource = function () { var self = this; this.extensions = ExtensionManager.extensions; this.sortedFullSet = Object.keys(this.extensions) .filter(function (key) { return self.extensions[key].installInfo && self.extensions[key].installInfo.locationType !== ExtensionManager.LOCATION_DEFAULT; }); this._sortFullSet(); this._setInitialFilter(); this._countUpdates(); return new $.Deferred().resolve().promise(); }; /** * @private * Re-sorts the current full set */ InstalledViewModel.prototype._sortFullSet = function () { var self = this; this.sortedFullSet = this.sortedFullSet.sort(function (key1, key2) { // before sorting by name, put first extensions that have updates var ua1 = self.extensions[key1].installInfo.updateAvailable, ua2 = self.extensions[key2].installInfo.updateAvailable; if (ua1 && !ua2) { return -1; } else if (!ua1 && ua2) { return 1; } var metadata1 = self.extensions[key1].installInfo.metadata, metadata2 = self.extensions[key2].installInfo.metadata, id1 = (metadata1.title || metadata1.name).toLocaleLowerCase(), id2 = (metadata2.title || metadata2.name).toLocaleLowerCase(); return id1.localeCompare(id2); }); }; /** * @private * Updates notifyCount based on number of extensions with an update available */ InstalledViewModel.prototype._countUpdates = function () { var self = this; this.notifyCount = 0; this.sortedFullSet.forEach(function (key) { if (self.extensions[key].installInfo.updateCompatible && !ExtensionManager.isMarkedForUpdate(key)) { self.notifyCount++; } }); }; /** * @private * Updates the initial set and filter as necessary when the status of an extension changes, * and notifies listeners of the change. * @param {$.Event} e The jQuery event object. * @param {string} id The id of the extension whose status changed. */ InstalledViewModel.prototype._handleStatusChange = function (e, id) { var index = this.sortedFullSet.indexOf(id), refilter = false; if (index !== -1 && !this.extensions[id].installInfo) { // This was in our set, but was uninstalled. Remove it. this.sortedFullSet.splice(index, 1); this._countUpdates(); // may also affect update count refilter = true; } else if (index === -1 && this.extensions[id].installInfo) { // This was not in our set, but is now installed. Add it and resort. this.sortedFullSet.push(id); this._sortFullSet(); refilter = true; } if (refilter) { this.filter(this._lastQuery || "", true); } if (this.extensions[id].installInfo) { // If our count of available updates may have been affected, re-count this._countUpdates(); } ExtensionManagerViewModel.prototype._handleStatusChange.call(this, e, id); }; /** * @private * Finds the extension metadata by id. If there is no extension matching the given id, * this returns `null`. * @param {string} id of the extension * @return {Object?} extension metadata or null if there's no matching extension */ InstalledViewModel.prototype._getEntry = function (id) { var entry = this.extensions[id]; if (entry) { return entry.installInfo; } return entry; }; /** * Model for displaying default extensions that come bundled with Brackets */ function DefaultViewModel() { ExtensionManagerViewModel.call(this); } // Inheritance setup DefaultViewModel.prototype = Object.create(ExtensionManagerViewModel.prototype); DefaultViewModel.prototype.constructor = DefaultViewModel; /** * Add SOURCE_DEFAULT to DefaultViewModel */ DefaultViewModel.prototype.source = ExtensionManagerViewModel.prototype.SOURCE_DEFAULT; /** * Initializes the model from the set of default extensions, sorted alphabetically by id * @return {$.Promise} a promise that's resolved when we're done initializing. */ DefaultViewModel.prototype._initializeFromSource = function () { var self = this; this.extensions = ExtensionManager.extensions; this.sortedFullSet = Object.keys(this.extensions) .filter(function (key) { return self.extensions[key].installInfo && self.extensions[key].installInfo.locationType === ExtensionManager.LOCATION_DEFAULT; }); this._sortFullSet(); this._setInitialFilter(); return new $.Deferred().resolve().promise(); }; /** * @private * Re-sorts the current full set */ DefaultViewModel.prototype._sortFullSet = function () { var self = this; this.sortedFullSet = this.sortedFullSet.sort(function (key1, key2) { var metadata1 = self.extensions[key1].installInfo.metadata, metadata2 = self.extensions[key2].installInfo.metadata, id1 = (metadata1.title || metadata1.name).toLocaleLowerCase(), id2 = (metadata2.title || metadata2.name).toLocaleLowerCase(); return id1.localeCompare(id2); }); }; /** * @private * Finds the default extension metadata by id. If there is no default extension matching the given id, * this returns `null`. * @param {string} id of the theme extension * @return {Object?} extension metadata or null if there's no matching extension */ DefaultViewModel.prototype._getEntry = function (id) { return this.extensions[id] ? this.extensions[id].installInfo : null; }; /** * The model for the ExtensionManagerView that is responsible for handling registry-based theme extensions. * This extends ExtensionManagerViewModel. * Must be disposed with dispose() when done. * * Events: * - change - triggered when the data for a given extension changes. Second parameter is the extension id. * - filter - triggered whenever the filtered set changes (including on initialize). * * @constructor */ function ThemesViewModel() { ExtensionManagerViewModel.call(this); } // Inheritance setup ThemesViewModel.prototype = Object.create(ExtensionManagerViewModel.prototype); ThemesViewModel.prototype.constructor = ThemesViewModel; /** * @type {string} * ThemeViewModels always have a source of SOURCE_THEMES. */ ThemesViewModel.prototype.source = ExtensionManagerViewModel.prototype.SOURCE_THEMES; /** * Initializes the model from the remote extension registry. * @return {$.Promise} a promise that's resolved with the registry JSON data. */ ThemesViewModel.prototype._initializeFromSource = function () { var self = this; return ExtensionManager.downloadRegistry() .done(function () { self.extensions = ExtensionManager.extensions; // Sort the registry by last published date and store the sorted list of IDs. self._setSortedExtensionList(ExtensionManager.extensions, true); self._setInitialFilter(); }) .fail(function () { self.extensions = []; self.sortedFullSet = []; self.filterSet = []; }); }; /** * @private * Finds the theme extension metadata by id. If there is no theme extension matching the given id, * this returns `null`. * @param {string} id of the theme extension * @return {Object?} extension metadata or null if there's no matching extension */ ThemesViewModel.prototype._getEntry = function (id) { var entry = this.extensions[id]; if (entry) { return entry.registryInfo; } return entry; }; exports.RegistryViewModel = RegistryViewModel; exports.ThemesViewModel = ThemesViewModel; exports.InstalledViewModel = InstalledViewModel; exports.DefaultViewModel = DefaultViewModel; }); ================================================ FILE: src/extensibility/InstallExtensionDialog.js ================================================ /* * Copyright (c) 2012 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /*unittests: Install Extension Dialog*/ define(function (require, exports, module) { "use strict"; var Dialogs = require("widgets/Dialogs"), File = require("filesystem/File"), StringUtils = require("utils/StringUtils"), Strings = require("strings"), FileSystem = require("filesystem/FileSystem"), KeyEvent = require("utils/KeyEvent"), Package = require("extensibility/Package"), NativeApp = require("utils/NativeApp"), InstallDialogTemplate = require("text!htmlContent/install-extension-dialog.html"), Mustache = require("thirdparty/mustache/mustache"); var STATE_CLOSED = 0, STATE_START = 1, STATE_VALID_URL = 2, STATE_INSTALLING = 3, STATE_INSTALLED = 4, STATE_INSTALL_FAILED = 5, STATE_CANCELING_INSTALL = 6, STATE_CANCELING_HUNG = 7, STATE_INSTALL_CANCELED = 8, STATE_ALREADY_INSTALLED = 9, STATE_OVERWRITE_CONFIRMED = 10, STATE_NEEDS_UPDATE = 11; /** * Creates a new extension installer dialog. * @constructor * @param {{install: function(url), cancel: function()}} installer The installer backend to use. */ function InstallExtensionDialog(installer, _isUpdate) { this._installer = installer; this._state = STATE_CLOSED; this._installResult = null; this._isUpdate = _isUpdate; // Timeout before we allow user to leave STATE_INSTALL_CANCELING without waiting for a resolution // (per-instance so we can poke it for unit testing) this._cancelTimeout = 10 * 1000; } /** * The dialog root. * @type {jQuery} */ InstallExtensionDialog.prototype.$dlg = null; /** * The url input field. * @type {jQuery} */ InstallExtensionDialog.prototype.$url = null; /** * The ok button. * @type {jQuery} */ InstallExtensionDialog.prototype.$okButton = null; /** * The cancel button. * @type {jQuery} */ InstallExtensionDialog.prototype.$cancelButton = null; /** * The area containing the url input label and field. * @type {jQuery} */ InstallExtensionDialog.prototype.$inputArea = null; /** * The area containing the installation message and spinner. * @type {jQuery} */ InstallExtensionDialog.prototype.$msgArea = null; /** * The span containing the installation message. * @type {jQuery} */ InstallExtensionDialog.prototype.$msg = null; /** * The "Browse Extensions" button. * @type {jQuery} */ InstallExtensionDialog.prototype.$browseExtensionsButton = null; /** * A deferred that's resolved/rejected when the dialog is closed and * something has/hasn't been installed successfully. * @type {$.Deferred} */ InstallExtensionDialog.prototype._dialogDeferred = null; /** * installer The installer backend for this dialog. * @type {{install: function(url), cancel: function()}} */ InstallExtensionDialog.prototype._installer = null; /** * The current state of the dialog; one of the STATE_* constants above. * @type {number} */ InstallExtensionDialog.prototype._state = null; /** * @private * Transitions the dialog into a new state as the installation proceeds. * @param {number} newState The state to transition into; one of the STATE_* variables. */ InstallExtensionDialog.prototype._enterState = function (newState) { var url, msg, self = this, prevState = this._state; // Store the new state up front in case some of the processing below ends up changing // the state again immediately. this._state = newState; switch (newState) { case STATE_START: // This should match the default appearance of the dialog when it first opens. this.$msg.find(".spinner").remove(); this.$msgArea.hide(); this.$inputArea.show(); this.$okButton .prop("disabled", true) .text(Strings.INSTALL); break; case STATE_VALID_URL: this.$okButton.prop("disabled", false); break; case STATE_INSTALLING: url = this.$url.val().trim(); this.$inputArea.hide(); this.$browseExtensionsButton.hide(); this.$msg.text(StringUtils.format(Strings.INSTALLING_FROM, url)) .append(""); this.$msgArea.show(); this.$okButton.prop("disabled", true); this._installer.install(url) .done(function (result) { self._installResult = result; if (result.installationStatus === Package.InstallationStatuses.ALREADY_INSTALLED || result.installationStatus === Package.InstallationStatuses.OLDER_VERSION || result.installationStatus === Package.InstallationStatuses.SAME_VERSION) { self._enterState(STATE_ALREADY_INSTALLED); } else if (result.installationStatus === Package.InstallationStatuses.NEEDS_UPDATE) { self._enterState(STATE_NEEDS_UPDATE); } else { self._enterState(STATE_INSTALLED); } }) .fail(function (err) { // If the "failure" is actually a user-requested cancel, don't show an error UI if (err === "CANCELED") { console.assert(self._state === STATE_CANCELING_INSTALL || self._state === STATE_CANCELING_HUNG); self._enterState(STATE_INSTALL_CANCELED); } else { self._errorMessage = Package.formatError(err); self._enterState(STATE_INSTALL_FAILED); } }); break; case STATE_CANCELING_INSTALL: // This should call back the STATE_INSTALLING fail() handler above, unless it's too late to cancel // in which case we'll still jump to STATE_INSTALLED after this this.$cancelButton.prop("disabled", true); this.$msg.text(Strings.CANCELING_INSTALL); this._installer.cancel(); window.setTimeout(function () { if (self._state === STATE_CANCELING_INSTALL) { self._enterState(STATE_CANCELING_HUNG); } }, this._cancelTimeout); break; case STATE_CANCELING_HUNG: this.$msg.text(Strings.CANCELING_HUNG); this.$okButton .removeAttr("disabled") .text(Strings.CLOSE); break; case STATE_INSTALLED: case STATE_INSTALL_FAILED: case STATE_INSTALL_CANCELED: case STATE_NEEDS_UPDATE: if (newState === STATE_INSTALLED) { msg = Strings.INSTALL_SUCCEEDED; } else if (newState === STATE_INSTALL_FAILED) { msg = Strings.INSTALL_FAILED; } else if (newState === STATE_NEEDS_UPDATE) { msg = Strings.EXTENSION_UPDATE_INSTALLED; } else { msg = Strings.INSTALL_CANCELED; } this.$msg.html($("").text(msg)); if (this._errorMessage) { this.$msg.append($("

      ").text(this._errorMessage)); } this.$okButton .removeAttr("disabled") .text(Strings.CLOSE); this.$cancelButton.hide(); break; case STATE_ALREADY_INSTALLED: var installResult = this._installResult; var status = installResult.installationStatus; var msgText = Strings["EXTENSION_" + status]; if (status === Package.InstallationStatuses.OLDER_VERSION) { msgText = StringUtils.format(msgText, installResult.metadata.version, installResult.installedVersion); } this.$msg.text(msgText); this.$okButton .prop("disabled", false) .text(Strings.OVERWRITE); break; case STATE_OVERWRITE_CONFIRMED: this._enterState(STATE_CLOSED); break; case STATE_CLOSED: $(window.document.body).off(".installDialog"); // Only resolve as successful if we actually installed something. Dialogs.cancelModalDialogIfOpen("install-extension-dialog"); if (prevState === STATE_INSTALLED || prevState === STATE_NEEDS_UPDATE || prevState === STATE_OVERWRITE_CONFIRMED) { this._dialogDeferred.resolve(this._installResult); } else { this._dialogDeferred.reject(); } break; } }; /** * @private * Handle a click on the Cancel button, which either cancels an ongoing installation (leaving * the dialog open), or closes the dialog if no installation is in progress. */ InstallExtensionDialog.prototype._handleCancel = function () { if (this._state === STATE_INSTALLING) { this._enterState(STATE_CANCELING_INSTALL); } else if (this._state === STATE_ALREADY_INSTALLED) { // If we were prompting the user about overwriting a previous installation, // and the user cancels, we can delete the downloaded file. if (this._installResult && this._installResult.localPath && !this._installResult.keepFile) { var filename = this._installResult.localPath; FileSystem.getFileForPath(filename).unlink(); } this._enterState(STATE_CLOSED); } else if (this._state !== STATE_CANCELING_INSTALL) { this._enterState(STATE_CLOSED); } }; /** * @private * Handle a click on the default button, which is "Install" while we're waiting for the * user to enter a URL, and "Close" once we've successfully finished installation. */ InstallExtensionDialog.prototype._handleOk = function () { if (this._state === STATE_INSTALLED || this._state === STATE_INSTALL_FAILED || this._state === STATE_INSTALL_CANCELED || this._state === STATE_CANCELING_HUNG || this._state === STATE_NEEDS_UPDATE) { // In these end states, this is a "Close" button: just close the dialog and indicate // success. this._enterState(STATE_CLOSED); } else if (this._state === STATE_VALID_URL) { this._enterState(STATE_INSTALLING); } else if (this._state === STATE_ALREADY_INSTALLED) { this._enterState(STATE_OVERWRITE_CONFIRMED); } }; /** * @private * Handle key up events on the document. We use this to detect the Esc key. */ InstallExtensionDialog.prototype._handleKeyUp = function (e) { if (e.keyCode === KeyEvent.DOM_VK_ESCAPE) { this._handleCancel(); } }; /** * @private * Handle typing in the URL field. */ InstallExtensionDialog.prototype._handleUrlInput = function (e) { var url = this.$url.val().trim(), valid = (url !== ""); if (!valid && this._state === STATE_VALID_URL) { this._enterState(STATE_START); } else if (valid && this._state === STATE_START) { this._enterState(STATE_VALID_URL); } }; /** * @private * Closes the dialog if it's not already closed. For unit testing only. */ InstallExtensionDialog.prototype._close = function () { if (this._state !== STATE_CLOSED) { this._enterState(STATE_CLOSED); } }; /** * Initialize and show the dialog. * @param {string=} urlToInstall If specified, immediately starts installing the given file as if the user had * specified it. * @return {$.Promise} A promise object that will be resolved when the selected extension * has finished installing, or rejected if the dialog is cancelled. */ InstallExtensionDialog.prototype.show = function (urlToInstall) { if (this._state !== STATE_CLOSED) { // Somehow the dialog got invoked twice. Just ignore this. return this._dialogDeferred.promise(); } var context = { Strings: Strings, isUpdate: this._isUpdate, includeBrowseExtensions: !!brackets.config.extension_listing_url }; // We ignore the promise returned by showModalDialogUsingTemplate, since we're managing the // lifecycle of the dialog ourselves. Dialogs.showModalDialogUsingTemplate(Mustache.render(InstallDialogTemplate, context), false); this.$dlg = $(".install-extension-dialog.instance"); this.$url = this.$dlg.find(".url").focus(); this.$okButton = this.$dlg.find(".dialog-button[data-button-id='ok']"); this.$cancelButton = this.$dlg.find(".dialog-button[data-button-id='cancel']"); this.$inputArea = this.$dlg.find(".input-field"); this.$msgArea = this.$dlg.find(".message-field"); this.$msg = this.$msgArea.find(".message"); this.$browseExtensionsButton = this.$dlg.find(".browse-extensions"); this.$okButton.on("click", this._handleOk.bind(this)); this.$cancelButton.on("click", this._handleCancel.bind(this)); this.$url.on("input", this._handleUrlInput.bind(this)); this.$browseExtensionsButton.on("click", function () { NativeApp.openURLInDefaultBrowser(brackets.config.extension_listing_url); }); $(window.document.body).on("keyup.installDialog", this._handleKeyUp.bind(this)); this._enterState(STATE_START); if (urlToInstall) { // Act as if the user had manually entered the URL. this.$url.val(urlToInstall); this._enterState(STATE_VALID_URL); this._enterState(STATE_INSTALLING); } this._dialogDeferred = new $.Deferred(); return this._dialogDeferred.promise(); }; /** Mediates between this module and the Package extension-installation utils. Mockable for unit-testing. */ function InstallerFacade(isLocalFile) { this._isLocalFile = isLocalFile; } InstallerFacade.prototype.install = function (url) { if (this.pendingInstall) { console.error("Extension installation already pending"); return new $.Deferred().reject("DOWNLOAD_ID_IN_USE").promise(); } if (this._isLocalFile) { var deferred = new $.Deferred(); this.pendingInstall = { promise: deferred.promise(), cancel: function () { // Can't cancel local zip installs } }; Package.installFromPath(url).then(function (installationResult) { // Flag to keep zip files for local file installation installationResult.keepFile = true; deferred.resolve(installationResult); }, deferred.reject); } else { this.pendingInstall = Package.installFromURL(url); } // Store now since we'll null pendingInstall immediately if the promise was resolved synchronously var promise = this.pendingInstall.promise; var self = this; this.pendingInstall.promise.always(function () { self.pendingInstall = null; }); return promise; }; InstallerFacade.prototype.cancel = function () { this.pendingInstall.cancel(); }; /** * @private * Show a dialog that allows the user to enter the URL of an extension ZIP file to install. * @return {$.Promise} A promise object that will be resolved when the selected extension * has finished installing, or rejected if the dialog is cancelled. */ function showDialog() { var dlg = new InstallExtensionDialog(new InstallerFacade()); return dlg.show(); } /** * @private * Show the installation dialog and automatically begin installing the given URL. * @param {(string|File)=} urlOrFileToInstall If specified, immediately starts installing the given file as if the user had * specified it. * @return {$.Promise} A promise object that will be resolved when the selected extension * has finished installing, or rejected if the dialog is cancelled. */ function installUsingDialog(urlOrFileToInstall, _isUpdate) { var isLocalFile = (urlOrFileToInstall instanceof File), dlg = new InstallExtensionDialog(new InstallerFacade(isLocalFile), _isUpdate); return dlg.show(urlOrFileToInstall.fullPath || urlOrFileToInstall); } /** * @private * Show the update dialog and automatically begin downloading the update from the given URL. * @param {string} urlToUpdate URL to download * @return {$.Promise} A promise object that will be resolved when the selected extension * has finished downloading, or rejected if the dialog is cancelled. */ function updateUsingDialog(urlToUpdate) { return installUsingDialog(urlToUpdate, true); } exports.showDialog = showDialog; exports.installUsingDialog = installUsingDialog; exports.updateUsingDialog = updateUsingDialog; // Exposed for unit testing only exports._Dialog = InstallExtensionDialog; }); ================================================ FILE: src/extensibility/Package.js ================================================ /* * Copyright (c) 2013 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /*jslint regexp: true */ /** * Functions for working with extension packages */ define(function (require, exports, module) { "use strict"; var AppInit = require("utils/AppInit"), FileSystem = require("filesystem/FileSystem"), FileUtils = require("file/FileUtils"), StringUtils = require("utils/StringUtils"), Strings = require("strings"), ExtensionLoader = require("utils/ExtensionLoader"), NodeConnection = require("utils/NodeConnection"), PreferencesManager = require("preferences/PreferencesManager"), PathUtils = require("thirdparty/path-utils/path-utils"); PreferencesManager.definePreference("proxy", "string", undefined, { description: Strings.DESCRIPTION_PROXY }); var PREF_EXTENSIONS_DEFAULT_DISABLED = "extensions.default.disabled"; PreferencesManager.definePreference(PREF_EXTENSIONS_DEFAULT_DISABLED, "array", [], { description: Strings.DESCRIPTION_DISABLED_DEFAULT_EXTENSIONS }); var Errors = { ERROR_LOADING: "ERROR_LOADING", MALFORMED_URL: "MALFORMED_URL", UNSUPPORTED_PROTOCOL: "UNSUPPORTED_PROTOCOL" }; var InstallationStatuses = { FAILED: "FAILED", INSTALLED: "INSTALLED", ALREADY_INSTALLED: "ALREADY_INSTALLED", SAME_VERSION: "SAME_VERSION", OLDER_VERSION: "OLDER_VERSION", NEEDS_UPDATE: "NEEDS_UPDATE", DISABLED: "DISABLED" }; /** * @private * @type {NodeConnection} * Connects to ExtensionManagerDomain */ var _nodeConnection; /** * @private * @type {jQuery.Deferred.} * A deferred which is resolved with a NodeConnection or rejected if * we are unable to connect to Node. */ var _nodeConnectionDeferred = $.Deferred(); /** * @type {number} Used to generate unique download ids */ var _uniqueId = 0; function _extensionManagerCall(callback) { if (_nodeConnection.domains.extensionManager) { return callback(_nodeConnection.domains.extensionManager); } else { return new $.Deferred().reject("extensionManager domain is undefined").promise(); } } /** * TODO: can this go away now that we never call it directly? * * Validates the package at the given path. The actual validation is * handled by the Node server. * * The promise is resolved with an object: * { errors: Array.<{string}>, metadata: { name:string, version:string, ... } } * metadata is pulled straight from package.json and will be undefined * if there are errors or null if the extension did not include package.json. * * @param {string} Absolute path to the package zip file * @param {{requirePackageJSON: ?boolean}} validation options * @return {$.Promise} A promise that is resolved with information about the package */ function validate(path, options) { return _extensionManagerCall(function (extensionManager) { var d = new $.Deferred(); // make sure proxy is attached to options before calling validate // so npm can use it in the domain options = options || {}; options.proxy = PreferencesManager.get("proxy"); extensionManager.validate(path, options) .done(function (result) { d.resolve({ errors: result.errors, metadata: result.metadata }); }) .fail(function (error) { d.reject(error); }); return d.promise(); }); } /** * Validates and installs the package at the given path. Validation and * installation is handled by the Node process. * * The extension will be installed into the user's extensions directory. * If the user already has the extension installed, it will instead go * into their disabled extensions directory. * * The promise is resolved with an object: * { errors: Array.<{string}>, metadata: { name:string, version:string, ... }, * disabledReason:string, installedTo:string, commonPrefix:string } * metadata is pulled straight from package.json and is likely to be undefined * if there are errors. It is null if there was no package.json. * * disabledReason is either null or the reason the extension was installed disabled. * * @param {string} path Absolute path to the package zip file * @param {?string} nameHint Hint for the extension folder's name (used in favor of * path's filename if present, and if no package metadata present). * @param {?boolean} _doUpdate private argument used to signal an update * @return {$.Promise} A promise that is resolved with information about the package * (which may include errors, in which case the extension was disabled), or * rejected with an error object. */ function install(path, nameHint, _doUpdate) { return _extensionManagerCall(function (extensionManager) { var d = new $.Deferred(), destinationDirectory = ExtensionLoader.getUserExtensionPath(), disabledDirectory = destinationDirectory.replace(/\/user$/, "/disabled"), systemDirectory = FileUtils.getNativeBracketsDirectoryPath() + "/extensions/default/"; var operation = _doUpdate ? "update" : "install"; extensionManager[operation](path, destinationDirectory, { disabledDirectory: disabledDirectory, systemExtensionDirectory: systemDirectory, apiVersion: brackets.metadata.apiVersion, nameHint: nameHint, proxy: PreferencesManager.get("proxy") }) .done(function (result) { result.keepFile = false; if (result.installationStatus !== InstallationStatuses.INSTALLED || _doUpdate) { d.resolve(result); } else { // This was a new extension and everything looked fine. // We load it into Brackets right away. ExtensionLoader.loadExtension(result.name, { // On Windows, it looks like Node converts Unix-y paths to backslashy paths. // We need to convert them back. baseUrl: FileUtils.convertWindowsPathToUnixPath(result.installedTo) }, "main").then(function () { d.resolve(result); }, function () { d.reject(Errors.ERROR_LOADING); }); } }) .fail(function (error) { d.reject(error); }); return d.promise(); }); } /** * Special case handling to make the common case of downloading from GitHub easier; modifies 'urlInfo' as * needed. Converts a bare GitHub repo URL to the corresponding master ZIP URL; or if given a direct * master ZIP URL already, sets a nicer download filename (both cases use the repo name). * * @param {{url:string, parsed:Array., filenameHint:string}} urlInfo */ function githubURLFilter(urlInfo) { if (urlInfo.parsed.hostname === "github.com" || urlInfo.parsed.hostname === "www.github.com") { // Is it a URL to the root of a repo? (/user/repo) var match = /^\/[^\/?]+\/([^\/?]+)(\/?)$/.exec(urlInfo.parsed.pathname); if (match) { if (!match[2]) { urlInfo.url += "/"; } urlInfo.url += "archive/master.zip"; urlInfo.filenameHint = match[1] + ".zip"; } else { // Is it a URL directly to the repo's 'master.zip'? (/user/repo/archive/master.zip) match = /^\/[^\/?]+\/([^\/?]+)\/archive\/master.zip$/.exec(urlInfo.parsed.pathname); if (match) { urlInfo.filenameHint = match[1] + ".zip"; } } } } /** * Downloads from the given URL to a temporary location. On success, resolves with the path of the * downloaded file (typically in a temp folder) and a hint for the real filename. On failure, rejects * with an error object. * * @param {string} url URL of the file to be downloaded * @param {number} downloadId Unique number to identify this request * @return {$.Promise} */ function download(url, downloadId) { return _extensionManagerCall(function (extensionManager) { var d = new $.Deferred(); // Validate URL // TODO: PathUtils fails to parse URLs that are missing the protocol part (e.g. starts immediately with "www...") var parsed = PathUtils.parseUrl(url); if (!parsed.hostname) { // means PathUtils failed to parse at all d.reject(Errors.MALFORMED_URL); return d.promise(); } if (parsed.protocol !== "http:" && parsed.protocol !== "https:") { d.reject(Errors.UNSUPPORTED_PROTOCOL); return d.promise(); } var urlInfo = { url: url, parsed: parsed, filenameHint: parsed.filename }; githubURLFilter(urlInfo); // Decide download destination var filename = urlInfo.filenameHint; filename = filename.replace(/[^a-zA-Z0-9_\- \(\)\.]/g, "_"); // make sure it's a valid filename if (!filename) { // in case of URL ending in "/" filename = "extension.zip"; } // Download the bits (using Node since brackets-shell doesn't support binary file IO) var r = extensionManager.downloadFile(downloadId, urlInfo.url, PreferencesManager.get("proxy")); r.done(function (result) { d.resolve({ localPath: FileUtils.convertWindowsPathToUnixPath(result), filenameHint: urlInfo.filenameHint }); }).fail(function (err) { d.reject(err); }); return d.promise(); }); } /** * Attempts to synchronously cancel the given pending download. This may not be possible, e.g. * if the download has already finished. * * @param {number} downloadId Identifier previously passed to download() */ function cancelDownload(downloadId) { return _extensionManagerCall(function (extensionManager) { return extensionManager.abortDownload(downloadId); }); } /** * On success, resolves with an extension metadata object; at that point, the extension has already * started running in Brackets. On failure (including validation errors), rejects with an error object. * * An error object consists of either a string error code OR an array where the first entry is the error * code and the remaining entries are further info. The error code string is one of either * ExtensionsDomain.Errors or Package.Errors. Use formatError() to convert an error object to a friendly, * localized error message. * * @param {string} path Absolute path to the package zip file * @param {?string} filenameHint Hint for the extension folder's name (used in favor of * path's filename if present, and if no package metadata present). * @return {$.Promise} A promise that is rejected if there are errors during * install or the extension is disabled. */ function installFromPath(path, filenameHint) { var d = new $.Deferred(); install(path, filenameHint) .done(function (result) { result.keepFile = true; var installationStatus = result.installationStatus; if (installationStatus === InstallationStatuses.ALREADY_INSTALLED || installationStatus === InstallationStatuses.NEEDS_UPDATE || installationStatus === InstallationStatuses.SAME_VERSION || installationStatus === InstallationStatuses.OLDER_VERSION) { d.resolve(result); } else { if (result.errors && result.errors.length > 0) { // Validation errors - for now, only return the first one d.reject(result.errors[0]); } else if (result.disabledReason) { // Extension valid but left disabled (wrong API version, extension name collision, etc.) d.reject(result.disabledReason); } else { // Success! Extension is now running in Brackets d.resolve(result); } } }) .fail(function (err) { d.reject(err); }); return d.promise(); } /** * On success, resolves with an extension metadata object; at that point, the extension has already * started running in Brackets. On failure (including validation errors), rejects with an error object. * * An error object consists of either a string error code OR an array where the first entry is the error * code and the remaining entries are further info. The error code string is one of either * ExtensionsDomain.Errors or Package.Errors. Use formatError() to convert an error object to a friendly, * localized error message. * * The returned cancel() function will *attempt* to cancel installation, but it is not guaranteed to * succeed. If cancel() succeeds, the Promise is rejected with a CANCELED error code. If we're unable * to cancel, the Promise is resolved or rejected normally, as if cancel() had never been called. * * @return {{promise: $.Promise, cancel: function():boolean}} */ function installFromURL(url) { var STATE_DOWNLOADING = 1, STATE_INSTALLING = 2, STATE_SUCCEEDED = 3, STATE_FAILED = 4; var d = new $.Deferred(); var state = STATE_DOWNLOADING; var downloadId = (_uniqueId++); download(url, downloadId) .done(function (downloadResult) { state = STATE_INSTALLING; installFromPath(downloadResult.localPath, downloadResult.filenameHint) .done(function (result) { var installationStatus = result.installationStatus; state = STATE_SUCCEEDED; result.localPath = downloadResult.localPath; result.keepFile = false; if (installationStatus === InstallationStatuses.INSTALLED) { // Delete temp file FileSystem.getFileForPath(downloadResult.localPath).unlink(); } d.resolve(result); }) .fail(function (err) { // File IO errors, internal error in install()/validate(), or extension startup crashed state = STATE_FAILED; FileSystem.getFileForPath(downloadResult.localPath).unlink(); d.reject(err); // TODO: needs to be err.message ? }); }) .fail(function (err) { // Download error (the Node-side download code cleans up any partial ZIP file) state = STATE_FAILED; d.reject(err); }); return { promise: d.promise(), cancel: function () { if (state === STATE_DOWNLOADING) { // This will trigger download()'s fail() handler with CANCELED as the err code cancelDownload(downloadId); } // Else it's too late to cancel; we'll continue on through the done() chain and emit // a success result (calling done() handlers) if all else goes well. } }; } /** * Converts an error object as returned by install(), installFromPath() or * installFromURL() into a flattened, localized string. * * @param {string|Array.} error * @return {string} */ function formatError(error) { function localize(key) { if (Strings[key]) { return Strings[key]; } console.log("Unknown installation error", key); return Strings.UNKNOWN_ERROR; } if (Array.isArray(error)) { error[0] = localize(error[0]); return StringUtils.format.apply(window, error); } else { return localize(error); } } /** * Removes the extension at the given path. * * @param {string} path The absolute path to the extension to remove. * @return {$.Promise} A promise that's resolved when the extension is removed, or * rejected if there was an error. */ function remove(path) { return _extensionManagerCall(function (extensionManager) { return extensionManager.remove(path); }); } /** * This function manages the PREF_EXTENSIONS_DEFAULT_DISABLED preference * holding an array of default extension paths that should not be loaded * on Brackets startup */ function toggleDefaultExtension(path, enabled) { var arr = PreferencesManager.get(PREF_EXTENSIONS_DEFAULT_DISABLED); if (!Array.isArray(arr)) { arr = []; } var io = arr.indexOf(path); if (enabled === true && io !== -1) { arr.splice(io, 1); } else if (enabled === false && io === -1) { arr.push(path); } PreferencesManager.set(PREF_EXTENSIONS_DEFAULT_DISABLED, arr); } /** * Disables the extension at the given path. * * @param {string} path The absolute path to the extension to disable. * @return {$.Promise} A promise that's resolved when the extenion is disabled, or * rejected if there was an error. */ function disable(path) { var result = new $.Deferred(), file = FileSystem.getFileForPath(path + "/.disabled"); var defaultExtensionPath = ExtensionLoader.getDefaultExtensionPath(); if (file.fullPath.indexOf(defaultExtensionPath) === 0) { toggleDefaultExtension(path, false); result.resolve(); return result.promise(); } file.write("", function (err) { if (err) { return result.reject(err); } result.resolve(); }); return result.promise(); } /** * Enables the extension at the given path. * * @param {string} path The absolute path to the extension to enable. * @return {$.Promise} A promise that's resolved when the extenion is enable, or * rejected if there was an error. */ function enable(path) { var result = new $.Deferred(), file = FileSystem.getFileForPath(path + "/.disabled"); function afterEnable() { ExtensionLoader.loadExtension(FileUtils.getBaseName(path), { baseUrl: path }, "main") .done(result.resolve) .fail(result.reject); } var defaultExtensionPath = ExtensionLoader.getDefaultExtensionPath(); if (file.fullPath.indexOf(defaultExtensionPath) === 0) { toggleDefaultExtension(path, true); afterEnable(); return result.promise(); } file.unlink(function (err) { if (err) { return result.reject(err); } afterEnable(); }); return result.promise(); } /** * Install an extension update located at path. * This assumes that the installation was previously attempted * and an installationStatus of "ALREADY_INSTALLED", "NEEDS_UPDATE", "SAME_VERSION", * or "OLDER_VERSION" was the result. * * This workflow ensures that there should not generally be validation errors * because the first pass at installation the extension looked at the metadata * and installed packages. * * @param {string} path to package file * @param {?string} nameHint Hint for the extension folder's name (used in favor of * path's filename if present, and if no package metadata present). * @return {$.Promise} A promise that is resolved when the extension is successfully * installed or rejected if there is a problem. */ function installUpdate(path, nameHint) { var d = new $.Deferred(); install(path, nameHint, true) .done(function (result) { if (result.installationStatus !== InstallationStatuses.INSTALLED) { d.reject(result.errors); } else { d.resolve(result); } }) .fail(function (error) { d.reject(error); }); return d.promise(); } /** * Allows access to the deferred that manages the node connection. This * is *only* for unit tests. Messing with this not in testing will * potentially break everything. * * @private * @return {jQuery.Deferred} The deferred that manages the node connection */ function _getNodeConnectionDeferred() { return _nodeConnectionDeferred; } // Initializes node connection // TODO: duplicates code from StaticServer // TODO: can this be done lazily? AppInit.appReady(function () { _nodeConnection = new NodeConnection(); _nodeConnection.connect(true).then(function () { var domainPath = FileUtils.getNativeBracketsDirectoryPath() + "/" + FileUtils.getNativeModuleDirectoryPath(module) + "/node/ExtensionManagerDomain"; _nodeConnection.loadDomains(domainPath, true) .then( function () { _nodeConnectionDeferred.resolve(); }, function () { // Failed to connect console.error("[Extensions] Failed to connect to node", arguments); _nodeConnectionDeferred.reject(); } ); }); }); // For unit tests only exports._getNodeConnectionDeferred = _getNodeConnectionDeferred; exports.installFromURL = installFromURL; exports.installFromPath = installFromPath; exports.validate = validate; exports.install = install; exports.remove = remove; exports.disable = disable; exports.enable = enable; exports.installUpdate = installUpdate; exports.formatError = formatError; exports.InstallationStatuses = InstallationStatuses; }); ================================================ FILE: src/extensibility/node/ExtensionManagerDomain.js ================================================ /* * Copyright (c) 2013 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /*eslint-env node */ /*jslint node: true */ "use strict"; var semver = require("semver"), path = require("path"), request = require("request"), fs = require("fs-extra"), temp = require("temp"), validate = require("./package-validator").validate; // Automatically clean up temp files on exit temp.track(); var Errors = { API_NOT_COMPATIBLE: "API_NOT_COMPATIBLE", MISSING_REQUIRED_OPTIONS: "MISSING_REQUIRED_OPTIONS", DOWNLOAD_ID_IN_USE: "DOWNLOAD_ID_IN_USE", BAD_HTTP_STATUS: "BAD_HTTP_STATUS", // {0} is the HTTP status code NO_SERVER_RESPONSE: "NO_SERVER_RESPONSE", CANNOT_WRITE_TEMP: "CANNOT_WRITE_TEMP", CANCELED: "CANCELED" }; var Statuses = { FAILED: "FAILED", INSTALLED: "INSTALLED", ALREADY_INSTALLED: "ALREADY_INSTALLED", SAME_VERSION: "SAME_VERSION", OLDER_VERSION: "OLDER_VERSION", NEEDS_UPDATE: "NEEDS_UPDATE", DISABLED: "DISABLED" }; /** * Maps unique download ID to info about the pending download. No entry if download no longer pending. * outStream is only present if we've started receiving the body. * @type {Object.} */ var pendingDownloads = {}; /** * Private function to remove the installation directory if the installation fails. * This does not call any callbacks. It's assumed that the callback has already been called * and this cleanup routine will do its best to complete in the background. If there's * a problem here, it is simply logged with console.error. * * @param {string} installDirectory Directory to remove */ function _removeFailedInstallation(installDirectory) { fs.remove(installDirectory, function (err) { if (err) { console.error("Error while removing directory after failed installation", installDirectory, err); } }); } /** * Private function to unzip to the correct directory. * * @param {string} Absolute path to the package zip file * @param {string} Absolute path to the destination directory for unzipping * @param {Object} the return value with the useful information for the client * @param {Function} callback function that is called at the end of the unzipping */ function _performInstall(packagePath, installDirectory, validationResult, callback) { validationResult.installedTo = installDirectory; function fail(err) { _removeFailedInstallation(installDirectory); callback(err, null); } function finish() { // The status may have already been set previously (as in the // DISABLED case. if (!validationResult.installationStatus) { validationResult.installationStatus = Statuses.INSTALLED; } callback(null, validationResult); } fs.mkdirs(installDirectory, function (err) { if (err) { callback(err); return; } var sourceDir = path.join(validationResult.extractDir, validationResult.commonPrefix); fs.copy(sourceDir, installDirectory, function (err) { if (err) { return fail(err); } finish(); }); }); } /** * Private function to remove the target directory and then install. * * @param {string} Absolute path to the package zip file * @param {string} Absolute path to the destination directory for unzipping * @param {Object} the return value with the useful information for the client * @param {Function} callback function that is called at the end of the unzipping */ function _removeAndInstall(packagePath, installDirectory, validationResult, callback) { // If this extension was previously installed but disabled, we will overwrite the // previous installation in that directory. fs.remove(installDirectory, function (err) { if (err) { callback(err); return; } _performInstall(packagePath, installDirectory, validationResult, callback); }); } function _checkExistingInstallation(validationResult, installDirectory, systemInstallDirectory, callback) { // If the extension being installed does not have a package.json, we can't // do any kind of version comparison, so we just signal to the UI that // it already appears to be installed. if (!validationResult.metadata) { validationResult.installationStatus = Statuses.ALREADY_INSTALLED; callback(null, validationResult); return; } fs.readJson(path.join(installDirectory, "package.json"), function (err, packageObj) { // if the package.json is unreadable, we assume that the new package is an update // that is the first to include a package.json. if (err) { validationResult.installationStatus = Statuses.NEEDS_UPDATE; } else { // Check to see if the version numbers signal an update. if (semver.lt(packageObj.version, validationResult.metadata.version)) { validationResult.installationStatus = Statuses.NEEDS_UPDATE; } else if (semver.gt(packageObj.version, validationResult.metadata.version)) { // Pass a message back to the UI that the new package appears to be an older version // than what's installed. validationResult.installationStatus = Statuses.OLDER_VERSION; validationResult.installedVersion = packageObj.version; } else { // Signal to the UI that it looks like the user is re-installing the // same version. validationResult.installationStatus = Statuses.SAME_VERSION; } } callback(null, validationResult); }); } /** * A "legacy package" is an extension that was installed based on the GitHub name without * a package.json file. Checking for the presence of these legacy extensions will help * users upgrade if the extension developer puts a different name in package.json than * the name of the GitHub project. * * @param {string} legacyDirectory directory to check for old-style extension. */ function legacyPackageCheck(legacyDirectory) { return fs.existsSync(legacyDirectory) && !fs.existsSync(path.join(legacyDirectory, "package.json")); } /** * Implements the "install" command in the "extensions" domain. * * There is no need to call validate independently. Validation is the first * thing that is done here. * * After the extension is validated, it is installed in destinationDirectory * unless the extension is already present there. If it is already present, * a determination is made about whether the package being installed is * an update. If it does appear to be an update, then result.installationStatus * is set to NEEDS_UPDATE. If not, then it's set to ALREADY_INSTALLED. * * If the installation succeeds, then result.installationStatus is set to INSTALLED. * * The extension is unzipped into a directory in destinationDirectory with * the name of the extension (the name is derived either from package.json * or the name of the zip file). * * The destinationDirectory will be created if it does not exist. * * @param {string} Absolute path to the package zip file * @param {string} the destination directory * @param {{disabledDirectory: !string, apiVersion: !string, nameHint: ?string, * systemExtensionDirectory: !string}} additional settings to control the installation * @param {function} callback (err, result) * @param {function} pCallback (msg) callback for notifications about operation progress * @param {boolean} _doUpdate private argument to signal that an update should be performed */ function _cmdInstall(packagePath, destinationDirectory, options, callback, pCallback, _doUpdate) { if (!options || !options.disabledDirectory || !options.apiVersion || !options.systemExtensionDirectory) { callback(new Error(Errors.MISSING_REQUIRED_OPTIONS), null); return; } function validateCallback(err, validationResult) { validationResult.localPath = packagePath; // This is a wrapper for the callback that will delete the temporary // directory to which the package was unzipped. function deleteTempAndCallback(err) { if (validationResult.extractDir) { fs.remove(validationResult.extractDir); delete validationResult.extractDir; } callback(err, validationResult); } // If there was trouble at the validation stage, we stop right away. if (err || validationResult.errors.length > 0) { validationResult.installationStatus = Statuses.FAILED; deleteTempAndCallback(err); return; } // Prefers the package.json name field, but will take the zip // file's name if that's all that's available. var extensionName, guessedName; if (options.nameHint) { guessedName = path.basename(options.nameHint, ".zip"); } else { guessedName = path.basename(packagePath, ".zip"); } if (validationResult.metadata) { extensionName = validationResult.metadata.name; } else { extensionName = guessedName; } validationResult.name = extensionName; var installDirectory = path.join(destinationDirectory, extensionName), legacyDirectory = path.join(destinationDirectory, guessedName), systemInstallDirectory = path.join(options.systemExtensionDirectory, extensionName); if (validationResult.metadata && validationResult.metadata.engines && validationResult.metadata.engines.brackets) { var compatible = semver.satisfies(options.apiVersion, validationResult.metadata.engines.brackets); if (!compatible) { installDirectory = path.join(options.disabledDirectory, extensionName); validationResult.installationStatus = Statuses.DISABLED; validationResult.disabledReason = Errors.API_NOT_COMPATIBLE; _removeAndInstall(packagePath, installDirectory, validationResult, deleteTempAndCallback); return; } } // The "legacy" stuff should go away after all of the commonly used extensions // have been upgraded with package.json files. var hasLegacyPackage = validationResult.metadata && legacyPackageCheck(legacyDirectory); // If the extension is already there, we signal to the front end that it's already installed // unless the front end has signaled an intent to update. if (hasLegacyPackage || fs.existsSync(installDirectory) || fs.existsSync(systemInstallDirectory)) { if (_doUpdate === true) { if (hasLegacyPackage) { // When there's a legacy installed extension, remove it first, // then also remove any new-style directory the user may have. // This helps clean up if the user is in a state where they have // both legacy and new extensions installed. fs.remove(legacyDirectory, function (err) { if (err) { deleteTempAndCallback(err); return; } _removeAndInstall(packagePath, installDirectory, validationResult, deleteTempAndCallback); }); } else { _removeAndInstall(packagePath, installDirectory, validationResult, deleteTempAndCallback); } } else if (hasLegacyPackage) { validationResult.installationStatus = Statuses.NEEDS_UPDATE; validationResult.name = guessedName; deleteTempAndCallback(null); } else { _checkExistingInstallation(validationResult, installDirectory, systemInstallDirectory, deleteTempAndCallback); } } else { // Regular installation with no conflicts. validationResult.disabledReason = null; _performInstall(packagePath, installDirectory, validationResult, deleteTempAndCallback); } } validate(packagePath, options, validateCallback); } /** * Implements the "update" command in the "extensions" domain. * * Currently, this just wraps _cmdInstall, but will remove the existing directory * first. * * There is no need to call validate independently. Validation is the first * thing that is done here. * * After the extension is validated, it is installed in destinationDirectory * unless the extension is already present there. If it is already present, * a determination is made about whether the package being installed is * an update. If it does appear to be an update, then result.installationStatus * is set to NEEDS_UPDATE. If not, then it's set to ALREADY_INSTALLED. * * If the installation succeeds, then result.installationStatus is set to INSTALLED. * * The extension is unzipped into a directory in destinationDirectory with * the name of the extension (the name is derived either from package.json * or the name of the zip file). * * The destinationDirectory will be created if it does not exist. * * @param {string} Absolute path to the package zip file * @param {string} the destination directory * @param {{disabledDirectory: !string, apiVersion: !string, nameHint: ?string, * systemExtensionDirectory: !string}} additional settings to control the installation * @param {function} callback (err, result) * @param {function} pCallback (msg) callback for notifications about operation progress */ function _cmdUpdate(packagePath, destinationDirectory, options, callback, pCallback) { _cmdInstall(packagePath, destinationDirectory, options, callback, pCallback, true); } /** * Wrap up after the given download has terminated (successfully or not). Closes connections, calls back the * client's callback, and IF there was an error, delete any partially-downloaded file. * * @param {string} downloadId Unique id originally passed to _cmdDownloadFile() * @param {?string} error If null, download was treated as successful */ function _endDownload(downloadId, error) { var downloadInfo = pendingDownloads[downloadId]; delete pendingDownloads[downloadId]; if (error) { // Abort the download if still pending // Note that this will trigger response's "end" event downloadInfo.request.abort(); // Clean up any partially-downloaded file // (if no outStream, then we never got a response back yet and never created any file) if (downloadInfo.outStream) { downloadInfo.outStream.end(function () { fs.unlink(downloadInfo.localPath); }); } downloadInfo.callback(error, null); } else { // Download completed successfully. Flush stream to disk and THEN signal completion downloadInfo.outStream.end(function () { downloadInfo.callback(null, downloadInfo.localPath); }); } } /** * Implements "downloadFile" command, asynchronously. */ function _cmdDownloadFile(downloadId, url, proxy, callback, pCallback) { // Backwards compatibility check, added in 0.37 if (typeof proxy === "function") { callback = proxy; proxy = undefined; } if (pendingDownloads[downloadId]) { callback(Errors.DOWNLOAD_ID_IN_USE, null); return; } var req = request.get({ url: url, encoding: null, proxy: proxy }, // Note: we could use the traditional "response"/"data"/"end" events too if we wanted to stream data // incrementally, limit download size, etc. - but the simple callback is good enough for our needs. function (error, response, body) { if (error) { // Usually means we never got a response - server is down, no DNS entry, etc. _endDownload(downloadId, Errors.NO_SERVER_RESPONSE); return; } if (response.statusCode !== 200) { _endDownload(downloadId, [Errors.BAD_HTTP_STATUS, response.statusCode]); return; } var stream = temp.createWriteStream("brackets"); if (!stream) { _endDownload(downloadId, Errors.CANNOT_WRITE_TEMP); return; } pendingDownloads[downloadId].localPath = stream.path; pendingDownloads[downloadId].outStream = stream; stream.write(body); _endDownload(downloadId); }); pendingDownloads[downloadId] = { request: req, callback: callback }; } /** * Implements "abortDownload" command, synchronously. */ function _cmdAbortDownload(downloadId) { if (!pendingDownloads[downloadId]) { // This may mean the download already completed return false; } else { _endDownload(downloadId, Errors.CANCELED); return true; } } /** * Implements the remove extension command. */ function _cmdRemove(extensionDir, callback, pCallback) { fs.remove(extensionDir, function (err) { if (err) { callback(err); } else { callback(null); } }); } /** * Initialize the "extensions" domain. * The extensions domain handles downloading, unpacking/verifying, and installing extensions. */ function init(domainManager) { if (!domainManager.hasDomain("extensionManager")) { domainManager.registerDomain("extensionManager", {major: 0, minor: 1}); } domainManager.registerCommand( "extensionManager", "validate", validate, true, "Verifies that the contents of the given ZIP file are a valid Brackets extension package", [{ name: "path", type: "string", description: "absolute filesystem path of the extension package" }, { name: "options", type: "{requirePackageJSON: ?boolean}", description: "options to control the behavior of the validator" }], [{ name: "errors", type: "string|Array.", description: "download error, if any; first string is error code (one of Errors.*); subsequent strings are additional info" }, { name: "metadata", type: "{name: string, version: string}", description: "all package.json metadata (null if there's no package.json)" }] ); domainManager.registerCommand( "extensionManager", "install", _cmdInstall, true, "Installs the given Brackets extension if it is valid (runs validation command automatically)", [{ name: "path", type: "string", description: "absolute filesystem path of the extension package" }, { name: "destinationDirectory", type: "string", description: "absolute filesystem path where this extension should be installed" }, { name: "options", type: "{disabledDirectory: !string, apiVersion: !string, nameHint: ?string, systemExtensionDirectory: !string, proxy: ?string}", description: "installation options: disabledDirectory should be set so that extensions can be installed disabled." }], [{ name: "errors", type: "string|Array.", description: "download error, if any; first string is error code (one of Errors.*); subsequent strings are additional info" }, { name: "metadata", type: "{name: string, version: string}", description: "all package.json metadata (null if there's no package.json)" }, { name: "disabledReason", type: "string", description: "reason this extension was installed disabled (one of Errors.*), none if it was enabled" }, { name: "installationStatus", type: "string", description: "Current status of the installation (an extension can be valid but not installed because it's an update" }, { name: "installedTo", type: "string", description: "absolute path where the extension was installed to" }, { name: "commonPrefix", type: "string", description: "top level directory in the package zip which contains all of the files" }] ); domainManager.registerCommand( "extensionManager", "update", _cmdUpdate, true, "Updates the given Brackets extension (for which install was generally previously attemped). Brackets must be quit after this.", [{ name: "path", type: "string", description: "absolute filesystem path of the extension package" }, { name: "destinationDirectory", type: "string", description: "absolute filesystem path where this extension should be installed" }, { name: "options", type: "{disabledDirectory: !string, apiVersion: !string, nameHint: ?string, systemExtensionDirectory: !string}", description: "installation options: disabledDirectory should be set so that extensions can be installed disabled." }], [{ name: "errors", type: "string|Array.", description: "download error, if any; first string is error code (one of Errors.*); subsequent strings are additional info" }, { name: "metadata", type: "{name: string, version: string}", description: "all package.json metadata (null if there's no package.json)" }, { name: "disabledReason", type: "string", description: "reason this extension was installed disabled (one of Errors.*), none if it was enabled" }, { name: "installationStatus", type: "string", description: "Current status of the installation (an extension can be valid but not installed because it's an update" }, { name: "installedTo", type: "string", description: "absolute path where the extension was installed to" }, { name: "commonPrefix", type: "string", description: "top level directory in the package zip which contains all of the files" }] ); domainManager.registerCommand( "extensionManager", "remove", _cmdRemove, true, "Removes the Brackets extension at the given path.", [{ name: "path", type: "string", description: "absolute filesystem path of the installed extension folder" }], {} ); domainManager.registerCommand( "extensionManager", "downloadFile", _cmdDownloadFile, true, "Downloads the file at the given URL, saving it to a temp location. Callback receives path to the downloaded file.", [{ name: "downloadId", type: "string", description: "Unique identifier for this download 'session'" }, { name: "url", type: "string", description: "URL to download from" }, { name: "proxy", type: "string", description: "optional proxy URL" }], { type: "string", description: "Local path to the downloaded file" } ); domainManager.registerCommand( "extensionManager", "abortDownload", _cmdAbortDownload, false, "Aborts any pending download with the given id. Ignored if no download pending (may be already complete).", [{ name: "downloadId", type: "string", description: "Unique identifier for this download 'session', previously pased to downloadFile" }], { type: "boolean", description: "True if the download was pending and able to be canceled; false otherwise" } ); } // used in unit tests exports._cmdValidate = validate; exports._cmdInstall = _cmdInstall; exports._cmdRemove = _cmdRemove; exports._cmdUpdate = _cmdUpdate; // used to load the domain exports.init = init; ================================================ FILE: src/extensibility/node/README.md ================================================ ## Brackets Extensibility This code is used by Brackets to implement its extension management features. It is likely not useful to anyone not working with Brackets extensions. ================================================ FILE: src/extensibility/node/npm-installer.js ================================================ /* * Copyright (c) 2013 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /* eslint-env node */ "use strict"; var fs = require("fs-extra"), path = require("path"), spawn = require("child_process").spawn; var Errors = { NPM_INSTALL_FAILED: "NPM_INSTALL_FAILED" }; /** * Private function to run "npm install --production" command in the extension directory. * * @param {string} installDirectory Directory to remove * @param {array} npmOptions can contain additional options like `--production` or `--proxy http://127.0.0.1:8888` * @param {function} callback NodeJS style callback to call after finish */ function _performNpmInstall(installDirectory, npmOptions, callback) { var npmPath = path.resolve(path.dirname(require.resolve("npm")), "..", "bin", "npm-cli.js"); var args = [npmPath, "install"].concat(npmOptions); console.log("running npm " + args.slice(1).join(" ") + " in " + installDirectory); var child = spawn(process.execPath, args, { cwd: installDirectory }); child.on("error", function (err) { return callback(err); }); var stdout = []; child.stdout.addListener("data", function (buffer) { stdout.push(buffer); }); var stderr = []; child.stderr.addListener("data", function (buffer) { stderr.push(buffer); }); var exitCode = 0; child.addListener("exit", function (code) { exitCode = code; }); child.addListener("close", function () { stderr = Buffer.concat(stderr).toString(); stdout = Buffer.concat(stdout).toString(); if (exitCode > 0) { console.error("npm-stderr: " + stderr); return callback(new Error(stderr)); } if (stderr) { console.warn("npm-stderr: " + stderr); } console.log("npm-stdout: " + stdout); return callback(); }); child.stdin.end(); } /** * Checks package.json of the extracted extension for npm dependencies * and runs npm install when required. * @param {Object} validationResult return value of the validation procedure * @param {Function} callback function to be called after the end of validation procedure */ function performNpmInstallIfRequired(npmOptions, validationResult, callback) { function finish() { callback(null, validationResult); } var installDirectory = path.join(validationResult.extractDir, validationResult.commonPrefix); var packageJson; try { packageJson = fs.readJsonSync(path.join(installDirectory, "package.json")); } catch (e) { packageJson = null; } if (!packageJson || !packageJson.dependencies || !Object.keys(packageJson.dependencies).length) { return finish(); } _performNpmInstall(installDirectory, npmOptions, function (err) { if (err) { validationResult.errors.push([Errors.NPM_INSTALL_FAILED, err.toString()]); } finish(); }); } exports.performNpmInstallIfRequired = performNpmInstallIfRequired; ================================================ FILE: src/extensibility/node/package-validator.js ================================================ /* * Copyright (c) 2013 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /*eslint-env node */ /*jslint node: true, regexp: true */ "use strict"; var DecompressZip = require("decompress-zip"), semver = require("semver"), path = require("path"), temp = require("temp"), fs = require("fs-extra"), performNpmInstallIfRequired = require("./npm-installer").performNpmInstallIfRequired; // Track and cleanup files at exit temp.track(); var Errors = { NOT_FOUND_ERR: "NOT_FOUND_ERR", // {0} is path where ZIP file was expected INVALID_ZIP_FILE: "INVALID_ZIP_FILE", // {0} is path to ZIP file INVALID_PACKAGE_JSON: "INVALID_PACKAGE_JSON", // {0} is JSON parse error, {1} is path to ZIP file MISSING_PACKAGE_NAME: "MISSING_PACKAGE_NAME", // {0} is path to ZIP file BAD_PACKAGE_NAME: "BAD_PACKAGE_NAME", // {0} is the name MISSING_PACKAGE_VERSION: "MISSING_PACKAGE_VERSION", // {0} is path to ZIP file INVALID_VERSION_NUMBER: "INVALID_VERSION_NUMBER", // {0} is version string in JSON, {1} is path to ZIP file MISSING_MAIN: "MISSING_MAIN", // {0} is path to ZIP file MISSING_PACKAGE_JSON: "MISSING_PACKAGE_JSON", // {0} is path to ZIP file INVALID_BRACKETS_VERSION: "INVALID_BRACKETS_VERSION", // {0} is the version string in JSON, {1} is the path to the zip file, DISALLOWED_WORDS: "DISALLOWED_WORDS" // {0} is the field with the word, {1} is a string list of words that were in violation, {2} is the path to the zip file }; /* * Directories to ignore when determining whether the contents of an extension are * in a subfolder. */ var ignoredFolders = [ "__MACOSX" ]; /** * Returns true if the name presented is acceptable as a package name. This enforces the * requirement as presented in the CommonJS spec: http://wiki.commonjs.org/wiki/Packages/1.0 * which states: * * "This must be a unique, lowercase alpha-numeric name without spaces. It may include "." or "_" or "-" characters." * * We add the additional requirement that the first character must be a letter or number * (there's a security implication to allowing a name like "..", because the name is * used in directory names). * * @param {string} name to test * @return {boolean} true if the name is valid */ function validateName(name) { if (/^[a-z0-9][a-z0-9._\-]*$/.exec(name)) { return true; } return false; } // Parses strings of the form "name (url)" where email and url are optional var _personRegex = /^([^<\(]+)(?:\s+<([^>]+)>)?(?:\s+\(([^\)]+)\))?$/; /** * Normalizes person fields from package.json. * * These fields can be an object with name, email and url properties or a * string of the form "name ". This does a tolerant parsing of * the data to try to return an object with name and optional email and url. * If the string does not match the format, the string is returned as the * name on the resulting object. * * If an object other than a string is passed in, it's returned as is. * * @param obj to normalize * @return {Object} person object with name and optional email and url */ function parsePersonString(obj) { if (typeof (obj) === "string") { var parts = _personRegex.exec(obj); // No regex match, so we just synthesize an object with an opaque name string if (!parts) { return { name: obj }; } else { var result = { name: parts[1] }; if (parts[2]) { result.email = parts[2]; } if (parts[3]) { result.url = parts[3]; } return result; } } else { // obj is not a string, so return as is return obj; } } /** * Determines if any of the words in wordlist appear in str. * * @param {String[]} wordlist list of words to check * @param {String} str to check for words * @return {String[]} words that matched */ function containsWords(wordlist, str) { var i; var matches = []; for (i = 0; i < wordlist.length; i++) { var re = new RegExp("\\b" + wordlist[i] + "\\b", "i"); if (re.exec(str)) { matches.push(wordlist[i]); } } return matches; } /** * Finds the common prefix, if any, for the files in a package file. * * In some package files, all of the files are contained in a subdirectory, and this function * will identify that directory if it exists. * * @param {string} extractDir directory into which the package was extracted * @param {function(Error, string)} callback function to accept err, commonPrefix (which will be "" if there is none) */ function findCommonPrefix(extractDir, callback) { fs.readdir(extractDir, function (err, files) { ignoredFolders.forEach(function (folder) { var index = files.indexOf(folder); if (index !== -1) { files.splice(index, 1); } }); if (err) { callback(err); } else if (files.length === 1) { var name = files[0]; if (fs.statSync(path.join(extractDir, name)).isDirectory()) { callback(null, name); } else { callback(null, ""); } } else { callback(null, ""); } }); } /** * Validates the contents of package.json. * * @param {string} path path to package file (used in error reporting) * @param {string} packageJSON path to the package.json file to check * @param {Object} options validation options passed to `validate()` * @param {function(Error, Array.>, Object)} callback function to call with array of errors and metadata */ function validatePackageJSON(path, packageJSON, options, callback) { var errors = []; if (fs.existsSync(packageJSON)) { fs.readFile(packageJSON, { encoding: "utf8" }, function (err, data) { if (err) { callback(err, null, null); return; } var metadata; try { metadata = JSON.parse(data); } catch (e) { errors.push([Errors.INVALID_PACKAGE_JSON, e.toString(), path]); callback(null, errors, undefined); return; } // confirm required fields in the metadata if (!metadata.name) { errors.push([Errors.MISSING_PACKAGE_NAME, path]); } else if (!validateName(metadata.name)) { errors.push([Errors.BAD_PACKAGE_NAME, metadata.name]); } if (!metadata.version) { errors.push([Errors.MISSING_PACKAGE_VERSION, path]); } else if (!semver.valid(metadata.version)) { errors.push([Errors.INVALID_VERSION_NUMBER, metadata.version, path]); } // normalize the author if (metadata.author) { metadata.author = parsePersonString(metadata.author); } // contributors should be an array of people. // normalize each entry. if (metadata.contributors) { if (metadata.contributors.map) { metadata.contributors = metadata.contributors.map(function (person) { return parsePersonString(person); }); } else { metadata.contributors = [ parsePersonString(metadata.contributors) ]; } } if (metadata.engines && metadata.engines.brackets) { var range = metadata.engines.brackets; if (!semver.validRange(range)) { errors.push([Errors.INVALID_BRACKETS_VERSION, range, path]); } } if (options.disallowedWords) { ["title", "description", "name"].forEach(function (field) { var words = containsWords(options.disallowedWords, metadata[field]); if (words.length > 0) { errors.push([Errors.DISALLOWED_WORDS, field, words.toString(), path]); } }); } callback(null, errors, metadata); }); } else { if (options.requirePackageJSON) { errors.push([Errors.MISSING_PACKAGE_JSON, path]); } callback(null, errors, null); } } /** * Extracts the package into the given directory and then validates it. * * @param {string} zipPath path to package zip file * @param {string} extractDir directory to extract package into * @param {Object} options validation options * @param {function(Error, {errors: Array, metadata: Object, commonPrefix: string, extractDir: string})} callback function to call with the result */ function extractAndValidateFiles(zipPath, extractDir, options, callback) { var unzipper = new DecompressZip(zipPath); unzipper.on("error", function (err) { // General error to report for problems reading the file callback(null, { errors: [[Errors.INVALID_ZIP_FILE, zipPath, err]] }); return; }); unzipper.on("extract", function (log) { findCommonPrefix(extractDir, function (err, commonPrefix) { if (err) { callback(err, null); return; } var packageJSON = path.join(extractDir, commonPrefix, "package.json"); validatePackageJSON(zipPath, packageJSON, options, function (err, errors, metadata) { if (err) { callback(err, null); return; } var mainJS = path.join(extractDir, commonPrefix, "main.js"), isTheme = metadata && metadata.theme; // Throw missing main.js file only for non-theme extensions if (!isTheme && !fs.existsSync(mainJS)) { errors.push([Errors.MISSING_MAIN, zipPath, mainJS]); } var npmOptions = ['--production']; if (options.proxy) { npmOptions.push('--proxy ' + options.proxy); } if (process.platform.startsWith('win')) { // On Windows force a 32 bit build until nodejs 64 bit is supported. npmOptions.push('--arch=ia32'); npmOptions.push('--npm_config_arch=ia32'); npmOptions.push('--npm_config_target_arch=ia32'); } performNpmInstallIfRequired(npmOptions, { errors: errors, metadata: metadata, commonPrefix: commonPrefix, extractDir: extractDir }, callback); }); }); }); unzipper.extract({ path: extractDir, filter: function (file) { return file.type !== "SymbolicLink"; } }); } /** * Implements the "validate" command in the "extensions" domain. * Validates the zipped package at path. * * The "err" parameter of the callback is only set if there was an * unexpected error. Otherwise, errors are reported in the result. * * The result object has an "errors" property. It is an array of * arrays of strings. Each array in the array is a set of parameters * that can be passed to StringUtils.format for internationalization. * The array will be empty if there are no errors. * * The result will have a "metadata" property if the metadata was * read successfully from package.json in the zip file. * * @param {string} path Absolute path to the package zip file * @param {{requirePackageJSON: ?boolean, disallowedWords: ?Array., proxy: ?}} options for validation * @param {function} callback (err, result) */ function validate(path, options, callback) { options = options || {}; fs.exists(path, function (doesExist) { if (!doesExist) { callback(null, { errors: [[Errors.NOT_FOUND_ERR, path]] }); return; } temp.mkdir("bracketsPackage_", function _tempDirCreated(err, extractDir) { if (err) { callback(err, null); return; } extractAndValidateFiles(path, extractDir, options, callback); }); }); } // exported for unit testing exports._parsePersonString = parsePersonString; exports.errors = Errors; exports.validate = validate; ================================================ FILE: src/extensibility/node/spec/Installation.spec.js ================================================ /* * Copyright (c) 2013 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /*eslint-env node */ /*jslint node: true */ /*global expect, describe, it, beforeEach, afterEach */ "use strict"; var ExtensionsDomain = require("../ExtensionManagerDomain"), fs = require("fs-extra"), async = require("async"), path = require("path"); var testFilesDirectory = path.join(path.dirname(module.filename), "..", // node "..", // extensibility "..", // src "..", // brackets "test", "spec", "extension-test-files"), installParent = path.join(path.dirname(module.filename), "extensions"), installDirectory = path.join(installParent, "good"), disabledDirectory = path.join(installParent, "disabled"), systemExtensionDirectory = path.join(installParent, "system"); var basicValidExtension = path.join(testFilesDirectory, "basic-valid-extension.zip"), basicValidExtension09 = path.join(testFilesDirectory, "basic-valid-extension-0.9.zip"), basicValidExtension2 = path.join(testFilesDirectory, "basic-valid-extension-2.0.zip"), missingMain = path.join(testFilesDirectory, "missing-main.zip"), oneLevelDown = path.join(testFilesDirectory, "one-level-extension-master.zip"), incompatibleVersion = path.join(testFilesDirectory, "incompatible-version.zip"), invalidZip = path.join(testFilesDirectory, "invalid-zip-file.zip"), missingPackageJSON = path.join(testFilesDirectory, "missing-package-json.zip"), missingPackageJSONUpdate = path.join(testFilesDirectory, "missing-package-json-update.zip"), missingPackageJSONRenamed = path.join(testFilesDirectory, "added-package-json-test", "missing-package-json.zip"), withSymlink = path.join(testFilesDirectory, "with-symlink.zip"), withNpmDependencies = path.join(testFilesDirectory, "with-npm-dependencies.zip"); describe("Package Installation", function () { var standardOptions = { disabledDirectory: disabledDirectory, systemExtensionDirectory: systemExtensionDirectory, apiVersion: "0.22.0" }; beforeEach(function (done) { fs.mkdirs(installDirectory, function (err) { fs.mkdirs(disabledDirectory, function (err) { done(); }); }); }); afterEach(function (done) { fs.remove(installParent, function (err) { done(); }); }); function checkPaths(pathsToCheck, callback) { var existsCalls = []; pathsToCheck.forEach(function (path) { existsCalls.push(function (callback) { fs.exists(path, async.apply(callback, null)); }); }); async.parallel(existsCalls, function (err, results) { expect(err).toBeNull(); results.forEach(function (result, num) { expect(result ? "" : pathsToCheck[num] + " does not exist").toEqual(""); }); callback(); }); } it("should validate the package", function (done) { ExtensionsDomain._cmdInstall(missingMain, installDirectory, standardOptions, function (err, result) { expect(err).toBeNull(); var errors = result.errors; expect(errors.length).toEqual(1); expect(result.installationStatus).toEqual("FAILED"); done(); }); }); it("should work fine if all is well", function (done) { ExtensionsDomain._cmdInstall(basicValidExtension, installDirectory, standardOptions, function (err, result) { var extensionDirectory = path.join(installDirectory, "basic-valid-extension"); expect(err).toBeNull(); var errors = result.errors; expect(errors.length).toEqual(0); expect(result.metadata.name).toEqual("basic-valid-extension"); expect(result.name).toEqual("basic-valid-extension"); expect(result.installedTo).toEqual(extensionDirectory); expect(result.installationStatus).toEqual("INSTALLED"); var pathsToCheck = [ path.join(extensionDirectory, "package.json"), path.join(extensionDirectory, "main.js") ]; checkPaths(pathsToCheck, done); }); }); it("should signal if an update installation is required", function (done) { ExtensionsDomain._cmdInstall(basicValidExtension, installDirectory, standardOptions, function (err, result) { var extensionDirectory = path.join(installDirectory, "basic-valid-extension"); expect(err).toBeNull(); expect(result.installedTo).toEqual(extensionDirectory); expect(result.installationStatus).toEqual("INSTALLED"); ExtensionsDomain._cmdInstall(basicValidExtension2, installDirectory, standardOptions, function (err, result) { expect(err).toBeNull(); expect(result.installationStatus).toEqual("NEEDS_UPDATE"); expect(result.localPath).toEqual(basicValidExtension2); done(); }); }); }); it("should successfully update an extension", function (done) { ExtensionsDomain._cmdInstall(basicValidExtension, installDirectory, standardOptions, function (err, result) { expect(err).toBeNull(); ExtensionsDomain._cmdInstall(basicValidExtension2, installDirectory, standardOptions, function (err, result) { expect(err).toBeNull(); expect(result.installationStatus).toBe("NEEDS_UPDATE"); ExtensionsDomain._cmdUpdate(basicValidExtension2, installDirectory, standardOptions, function (err, result) { expect(err).toBeNull(); expect(result.installationStatus).toBe("INSTALLED"); expect(result.installedTo.substr(0, installDirectory.length)).toEqual(installDirectory); expect(fs.existsSync(result.installedTo)).toBe(true); var packageInfo = fs.readJsonSync(path.join(result.installedTo, "package.json")); expect(packageInfo.version).toBe("2.0.0"); done(); }); }); }); }); it("should signal an update if a package.json appears", function (done) { ExtensionsDomain._cmdInstall(missingPackageJSON, installDirectory, standardOptions, function (err, result) { expect(err).toBeNull(); expect(result.installationStatus).toEqual("INSTALLED"); ExtensionsDomain._cmdInstall(missingPackageJSONUpdate, installDirectory, standardOptions, function (err, result) { expect(err).toBeNull(); expect(result.installationStatus).toEqual("NEEDS_UPDATE"); done(); }); }); }); // This is mildly redundant. the validation check should catch this. // But, I wanted to be sure that the install function doesn't try to // do anything with the file before validation. it("should fail for missing package", function (done) { ExtensionsDomain._cmdInstall(path.join(testFilesDirectory, "NOT A PACKAGE"), installDirectory, standardOptions, function (err, result) { expect(err).toBeNull(); var errors = result.errors; expect(errors.length).toEqual(1); expect(errors[0][0]).toEqual("NOT_FOUND_ERR"); done(); }); }); it("should not install by default if the same version is already installed", function (done) { ExtensionsDomain._cmdInstall(basicValidExtension, installDirectory, standardOptions, function (err, result) { expect(err).toBeNull(); ExtensionsDomain._cmdInstall(basicValidExtension, installDirectory, standardOptions, function (err, result) { expect(err).toBeNull(); expect(result.installationStatus).toEqual("SAME_VERSION"); done(); }); }); }); it("should not install by default if an older version is already installed", function (done) { ExtensionsDomain._cmdInstall(basicValidExtension, installDirectory, standardOptions, function (err, result) { expect(err).toBeNull(); ExtensionsDomain._cmdInstall(basicValidExtension09, installDirectory, standardOptions, function (err, result) { expect(err).toBeNull(); expect(result.installationStatus).toEqual("OLDER_VERSION"); done(); }); }); }); it("should not install by default if the same legacy extension is already installed", function (done) { ExtensionsDomain._cmdInstall(missingPackageJSON, installDirectory, standardOptions, function (err, result) { expect(err).toBeNull(); ExtensionsDomain._cmdInstall(missingPackageJSON, installDirectory, standardOptions, function (err, result) { expect(err).toBeNull(); expect(result.installationStatus).toEqual("ALREADY_INSTALLED"); done(); }); }); }); it("should yield an error if there's no disabled directory set", function (done) { ExtensionsDomain._cmdInstall(basicValidExtension, installDirectory, { apiVersion: "0.22.0" }, function (err, result) { expect(err.message).toEqual("MISSING_REQUIRED_OPTIONS"); done(); }); }); it("should yield an error if there's no apiVersion set", function (done) { ExtensionsDomain._cmdInstall(basicValidExtension, installDirectory, { disabledDirectory: disabledDirectory }, function (err, result) { expect(err.message).toEqual("MISSING_REQUIRED_OPTIONS"); done(); }); }); it("should derive the name from the zip if there's no package.json", function (done) { ExtensionsDomain._cmdInstall(missingPackageJSON, installDirectory, standardOptions, function (err, result) { expect(err).toBeNull(); expect(result.disabledReason).toBeNull(); var extensionDirectory = path.join(installDirectory, "missing-package-json"); var pathsToCheck = [ path.join(extensionDirectory, "main.js") ]; checkPaths(pathsToCheck, done); }); }); it("should install with the common prefix removed", function (done) { ExtensionsDomain._cmdInstall(oneLevelDown, installDirectory, standardOptions, function (err, result) { expect(err).toBeNull(); var extensionDirectory = path.join(installDirectory, "one-level-extension"); var pathsToCheck = [ path.join(extensionDirectory, "main.js"), path.join(extensionDirectory, "package.json"), path.join(extensionDirectory, "lib", "foo.js") ]; checkPaths(pathsToCheck, done); }); }); it("should disable extensions that are not compatible with the current Brackets API", function (done) { ExtensionsDomain._cmdInstall(incompatibleVersion, installDirectory, standardOptions, function (err, result) { expect(err).toBeNull(); expect(result.installationStatus).toEqual("DISABLED"); expect(result.disabledReason).toEqual("API_NOT_COMPATIBLE"); var extensionDirectory = path.join(disabledDirectory, "incompatible-version"); var pathsToCheck = [ path.join(extensionDirectory, "main.js"), path.join(extensionDirectory, "package.json") ]; checkPaths(pathsToCheck, done); }); }); it("should not have trouble with invalid zip files", function (done) { ExtensionsDomain._cmdInstall(invalidZip, installDirectory, standardOptions, function (err, result) { expect(err).toBeNull(); expect(result.errors.length).toEqual(1); done(); }); }); it("should remove an installed package", function (done) { ExtensionsDomain._cmdInstall(basicValidExtension, installDirectory, standardOptions, function (err, result) { expect(err).toBeNull(); expect(fs.existsSync(result.installedTo)).toBe(true); ExtensionsDomain._cmdRemove(result.installedTo, function (err) { expect(err).toBeNull(); expect(fs.existsSync(result.installedTo)).toBe(false); done(); }); }); }); it("should handle a package renamed with package.json", function (done) { ExtensionsDomain._cmdInstall(missingPackageJSON, installDirectory, standardOptions, function (err, result) { expect(err).toBeNull(); expect(fs.existsSync(result.installedTo)).toBe(true); var legacyDirectory = result.installedTo; ExtensionsDomain._cmdInstall(missingPackageJSONRenamed, installDirectory, standardOptions, function (err, result) { expect(err).toBeNull(); expect(result.installationStatus).toBe("NEEDS_UPDATE"); expect(result.name).toBe("missing-package-json"); ExtensionsDomain._cmdUpdate(missingPackageJSONRenamed, installDirectory, standardOptions, function (err, result) { expect(err).toBeNull(); expect(result.installationStatus).toBe("INSTALLED"); expect(result.name).toBe("renamed-in-package-json"); expect(fs.existsSync(legacyDirectory)).toBe(false); done(); }); }); }); }); it("should strip out symlinks in the zipfile", function (done) { ExtensionsDomain._cmdInstall(withSymlink, installDirectory, standardOptions, function (err, result) { expect(err).toBeNull(); expect(result.errors.length).toEqual(0); expect(fs.existsSync(result.installedTo)).toBe(true); expect(fs.existsSync(path.join(result.installedTo, "bin", "foo"))).toBe(false); done(); }); }); it("should download npm dependencies when present", function (done) { ExtensionsDomain._cmdInstall(withNpmDependencies, installDirectory, standardOptions, function (err, result) { expect(err).toBeNull(); expect(result.errors.length).toEqual(0); expect(fs.existsSync(result.installedTo)).toBe(true); expect(fs.existsSync(path.join(result.installedTo, "node_modules"))).toBe(true); expect(fs.existsSync(path.join(result.installedTo, "node_modules", "lodash"))).toBe(true); expect(fs.existsSync(path.join(result.installedTo, "node_modules", "lodash", "package.json"))).toBe(true); var packageInfo = JSON.parse(fs.readFileSync(path.join(result.installedTo, "node_modules", "lodash", "package.json"))); expect(packageInfo.version.slice(0,2)).toBe("3."); expect(fs.existsSync(path.join(result.installedTo, "node_modules", "moment"))).toBe(true); expect(fs.existsSync(path.join(result.installedTo, "node_modules", "moment", "package.json"))).toBe(true); packageInfo = JSON.parse(fs.readFileSync(path.join(result.installedTo, "node_modules", "moment", "package.json"))); expect(packageInfo.version.slice(0,4)).toBe("2.5."); expect(fs.existsSync(path.join(result.installedTo, "node_modules", "underscore"))).toBe(true); expect(fs.existsSync(path.join(result.installedTo, "node_modules", "underscore", "package.json"))).toBe(true); packageInfo = JSON.parse(fs.readFileSync(path.join(result.installedTo, "node_modules", "underscore", "package.json"))); expect(packageInfo.version).toBe("1.0.4"); done(); }); }); }); ================================================ FILE: src/extensibility/node/spec/Validation.spec.js ================================================ /* * Copyright (c) 2013 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /*eslint-env node */ /*jslint node: true */ /*global expect, describe, it, afterEach */ "use strict"; var rewire = require("rewire"), packageValidator = rewire("../package-validator"), path = require("path"), temp = require("temp"); var testFilesDirectory = path.join(path.dirname(module.filename), "..", // node "..", // extensibility "..", // src "..", // brackets "test", "spec", "extension-test-files"); var basicValidExtension = path.join(testFilesDirectory, "basic-valid-extension.zip"), basicValidExtension2 = path.join(testFilesDirectory, "basic-valid-extension-2.0.zip"), basicValidTheme = path.join(testFilesDirectory, "basic-valid-theme-1.0.zip"), missingPackageJSON = path.join(testFilesDirectory, "missing-package-json.zip"), invalidJSON = path.join(testFilesDirectory, "invalid-json.zip"), invalidZip = path.join(testFilesDirectory, "invalid-zip-file.zip"), missingNameVersion = path.join(testFilesDirectory, "missing-name-version.zip"), missingMain = path.join(testFilesDirectory, "missing-main.zip"), oneLevelDown = path.join(testFilesDirectory, "one-level-extension-master.zip"), bogusTopDir = path.join(testFilesDirectory, "bogus-top-dir.zip"), badname = path.join(testFilesDirectory, "badname.zip"), mainInDirectory = path.join(testFilesDirectory, "main-in-directory.zip"), invalidVersion = path.join(testFilesDirectory, "invalid-version.zip"), invalidBracketsVersion = path.join(testFilesDirectory, "invalid-brackets-version.zip"), ignoredFolder = path.join(testFilesDirectory, "has-macosx.zip"); describe("Package Validation", function () { afterEach(function () { temp.cleanup(); }); it("should handle a good package", function (done) { packageValidator.validate(basicValidExtension, {}, function (err, result) { expect(err).toBeNull(); expect(result.extractDir).toBeDefined(); expect(result.errors.length).toEqual(0); var metadata = result.metadata; expect(metadata.name).toEqual("basic-valid-extension"); expect(metadata.version).toEqual("1.0.0"); expect(metadata.title).toEqual("Basic Valid Extension"); expect(metadata.author.name).toEqual("Alfred Einstein"); expect(metadata.author.email).toEqual("alfred_not_albert@thoseeinsteins.org"); expect(metadata.author.url).toBeUndefined(); done(); }); }); it("should NOT complain about missing package.json", function (done) { packageValidator.validate(missingPackageJSON, {}, function (err, result) { expect(err).toBeNull(); var errors = result.errors; expect(errors.length).toEqual(0); expect(result.metadata).toBeNull(); done(); }); }); it("should complain about missing package.json if you tell it to", function (done) { packageValidator.validate(missingPackageJSON, { requirePackageJSON: true }, function (err, result) { expect(err).toBeNull(); var errors = result.errors; expect(errors.length).toEqual(1); expect(errors[0][0]).toEqual("MISSING_PACKAGE_JSON"); done(); }); }); it("should complain about illegal path", function (done) { packageValidator.validate(path.join(testFilesDirectory, "NO_FILE_HERE"), {}, function (err, result) { expect(err).toBeNull(); var errors = result.errors; expect(errors.length).toEqual(1); expect(errors[0][0]).toEqual("NOT_FOUND_ERR"); expect(result.metadata).toBeUndefined(); done(); }); }); it("should complain about invalid JSON", function (done) { packageValidator.validate(invalidJSON, {}, function (err, result) { expect(err).toBeNull(); var errors = result.errors; expect(errors.length).toEqual(1); expect(errors[0][0]).toEqual("INVALID_PACKAGE_JSON"); expect(errors[0][1]).toEqual("SyntaxError: Unexpected token I"); expect(errors[0][2]).toEqual(invalidJSON); expect(result.metadata).toBeUndefined(); done(); }); }); it("should complain about an invalid zip file", function (done) { packageValidator.validate(invalidZip, {}, function (err, result) { expect(err).toBeNull(); var errors = result.errors; expect(errors.length).toEqual(1); expect(errors[0][0]).toEqual("INVALID_ZIP_FILE"); done(); }); }); it("should require name and version in the metadata", function (done) { packageValidator.validate(missingNameVersion, {}, function (err, result) { expect(err).toBeNull(); var errors = result.errors; expect(errors.length).toEqual(2); expect(errors[0][0]).toEqual("MISSING_PACKAGE_NAME"); expect(errors[1][0]).toEqual("MISSING_PACKAGE_VERSION"); done(); }); }); it("should validate the version number", function (done) { packageValidator.validate(invalidVersion, {}, function (err, result) { expect(err).toBeNull(); var errors = result.errors; expect(errors.length).toEqual(1); expect(errors[0][0]).toEqual("INVALID_VERSION_NUMBER"); expect(errors[0][1]).toEqual("NOT A VERSION"); done(); }); }); it("should require a main.js in the zip file", function (done) { packageValidator.validate(missingMain, {}, function (err, result) { expect(err).toBeNull(); var errors = result.errors; expect(errors.length).toEqual(1); expect(errors[0][0]).toEqual("MISSING_MAIN"); done(); }); }); it("should NOT require a main.js in the zip file for a theme", function (done) { packageValidator.validate(basicValidTheme, {}, function (err, result) { expect(err).toBeNull(); expect(result.errors.length).toEqual(0); expect(result.metadata.theme).toBeDefined(); done(); }); }); it("should determine the common prefix if there is one", function (done) { packageValidator.validate(oneLevelDown, {}, function (err, result) { expect(err).toBeNull(); expect(result.errors.length).toEqual(0); expect(result.metadata.name).toEqual("one-level-extension"); expect(result.commonPrefix).toEqual("one-level-extension-master"); expect(result.metadata.author.name).toEqual("A Person"); done(); }); }); it("should not be fooled by bogus top directories", function (done) { packageValidator.validate(bogusTopDir, {}, function (err, result) { expect(err).toBeNull(); expect(result.errors.length).toEqual(0); expect(result.metadata.name).toEqual("bogus-top-dir"); expect(result.commonPrefix).toEqual(""); done(); }); }); it("should not allow names that contain disallowed characters", function (done) { packageValidator.validate(badname, {}, function (err, result) { expect(err).toBeNull(); expect(result.errors.length).toEqual(1); expect(result.errors[0][0]).toEqual("BAD_PACKAGE_NAME"); done(); }); }); it("should complain about files that don't have main in the top dir", function (done) { packageValidator.validate(mainInDirectory, {}, function (err, result) { expect(err).toBeNull(); var errors = result.errors; expect(errors.length).toEqual(1); expect(errors[0][0]).toEqual("MISSING_MAIN"); done(); }); }); it("should handle a variety of person forms", function () { var parse = packageValidator._parsePersonString; expect(parse("A Person")).toEqual({ name: "A Person" }); expect(parse({ name: "A Person" })).toEqual({ name: "A Person" }); expect(parse("A Person (http://foo.bar)")).toEqual({ name: "A Person", url: "http://foo.bar" }); expect(parse("A Person ")).toEqual({ name: "A Person", email: "foo@bar" }); expect(parse("A Person (http://foo.bar)")).toEqual({ name: "A Person", email: "foo@bar", url: "http://foo.bar" }); }); it("should handle contributors", function (done) { packageValidator.validate(basicValidExtension2, {}, function (err, result) { expect(err).toBeNull(); expect(result.errors.length).toEqual(0); var contrib = result.metadata.contributors; expect(contrib.length).toEqual(3); expect(contrib[0]).toEqual({ name: "Johan Einstein" }); expect(contrib[1]).toEqual({ name: "Albert Einstein Jr.", email: "not_that_albert@thoseeinsteins.org" }); expect(contrib[2]).toEqual({ name: "Jens Einstein", email: "jens@thoseeinsteins.org" }); done(); }); }); it("should validate the Brackets version", function (done) { packageValidator.validate(invalidBracketsVersion, {}, function (err, result) { expect(err).toBeNull(); expect(result.errors.length).toEqual(1); expect(result.errors[0][0]).toEqual("INVALID_BRACKETS_VERSION"); expect(result.errors[0][1]).toEqual("foo"); done(); }); }); it("should reject a package with rejected words in title or description", function (done) { packageValidator.validate(basicValidExtension, { disallowedWords: ["valid"] }, function (err, result) { expect(err).toBeNull(); expect(result.errors.length).toEqual(2); expect(result.errors[0][0]).toEqual("DISALLOWED_WORDS"); expect(result.errors[0][1]).toEqual("title"); expect(result.errors[0][2]).toEqual("valid"); expect(result.errors[1][0]).toEqual("DISALLOWED_WORDS"); expect(result.errors[1][1]).toEqual("name"); expect(result.errors[1][2]).toEqual("valid"); done(); }); }); it("should only allow certain characters in the name", function () { var validateName = packageValidator.__get__("validateName"); expect(validateName("Foo")).toEqual(false); expect(validateName("foo")).toEqual(true); expect(validateName("foo2")).toEqual(true); expect(validateName("foo.bar")).toEqual(true); expect(validateName("foo-bar")).toEqual(true); expect(validateName("foo_bar")).toEqual(true); expect(validateName("foo&bar")).toEqual(false); expect(validateName("foo/bar")).toEqual(false); expect(validateName("..")).toEqual(false); expect(validateName(".")).toEqual(false); }); it("should ignore the __MACOSX folder when looking for a single subfolder", function (done) { packageValidator.validate(ignoredFolder, {}, function (err, result) { expect(err).toBeNull(); expect(result.errors.length).toEqual(0); done(); }); }); }); ================================================ FILE: src/extensibility/registry_utils.js ================================================ /* * Copyright (c) 2013 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /* * N.B.: This file was copied from `lib/registry_utils.js` in `https://github.com/adobe/brackets-registry`. * We can't use the exact same file currently because Brackets uses AMD-style modules, so this version has * the AMD wrapper added (and is reindented to avoid JSLint complaints).. If changes are made here, the * version in the registry app should be kept in sync. * In the future, we should have a better mechanism for sharing code between the two. */ define(function (require, exports, module) { "use strict"; // From Brackets StringUtils function htmlEscape(str) { return String(str) .replace(/&/g, "&") .replace(/"/g, """) .replace(/'/g, "'") .replace(//g, ">"); } /** * Gets the last version from the given object and returns the short form of its date. * Assumes "this" is the current template context. * @return {string} The formatted date. */ exports.lastVersionDate = function () { var result; if (this.versions && this.versions.length) { result = this.versions[this.versions.length - 1].published; if (result) { result = new Date(result); result = result.toLocaleDateString(brackets.getLocale(), { "year": "numeric", "month": "2-digit", "day": "2-digit" }); } } return result || ""; }; /** * Returns a more friendly display form of the owner's internal user id. * Assumes "this" is the current template context. * @return {string} A display version in the form "id (service)". */ exports.formatUserId = function () { var friendlyName; if (this.owner) { var nameComponents = this.owner.split(":"); friendlyName = nameComponents[1]; } return friendlyName; }; /** * Given a registry item, returns a URL that represents its owner's page on the auth service. * Currently only handles GitHub. * Assumes "this" is the current template context. * @return {string} A link to that user's page on the service. */ exports.ownerLink = function () { var url; if (this.owner) { var nameComponents = this.owner.split(":"); if (nameComponents[0] === "github") { url = "https://github.com/" + nameComponents[1]; } } return url; }; /** * Given a registry item, formats the author information, including a link to the owner's * github page (if available) and the author's name from the metadata. */ exports.authorInfo = function () { var result = "", ownerLink = exports.ownerLink.call(this), userId = exports.formatUserId.call(this); if (this.metadata && this.metadata.author) { result = htmlEscape(this.metadata.author.name || this.metadata.author); } else if (userId) { result = htmlEscape(userId); } if (ownerLink) { result = "" + result + ""; } return result; }; /** * Returns an array of current registry entries, sorted by the publish date of the latest version of each entry. * @param {object} registry The unsorted registry. * @param {string} subkey The subkey to look for the registry metadata in. If unspecified, assumes * we should look at the top level of the object. * @return {Array} Sorted array of registry entries. */ exports.sortRegistry = function (registry, subkey, sortBy) { function getPublishTime(entry) { if (entry.versions) { return new Date(entry.versions[entry.versions.length - 1].published).getTime(); } return Number.NEGATIVE_INFINITY; } var sortedEntries = []; // Sort the registry by last published date (newest first). Object.keys(registry).forEach(function (key) { sortedEntries.push(registry[key]); }); sortedEntries.sort(function (entry1, entry2) { if (sortBy !== "publishedDate") { if (entry1.registryInfo && entry2.registryInfo) { return entry2.registryInfo.totalDownloads - entry1.registryInfo.totalDownloads; } else { return Number.NEGATIVE_INFINITY; } } else { return getPublishTime((subkey && entry2[subkey]) || entry2) - getPublishTime((subkey && entry1[subkey]) || entry1); } }); return sortedEntries; }; }); ================================================ FILE: src/extensions/default/AutoUpdate/MessageIds.js ================================================ /* * Copyright (c) 2018 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ define(function (require, exports, module) { "use strict"; exports.DOWNLOAD_INSTALLER = "node.downloadInstaller"; exports.PERFORM_CLEANUP = "node.performCleanup"; exports.VALIDATE_INSTALLER = "node.validateInstaller"; exports.INITIALIZE_STATE = "node.initializeState"; exports.CHECK_INSTALLER_STATUS = "node.checkInstallerStatus"; exports.REMOVE_FROM_REQUESTERS = "node.removeFromRequesters"; exports.SHOW_STATUS_INFO = "brackets.showStatusInfo"; exports.NOTIFY_DOWNLOAD_SUCCESS = "brackets.notifyDownloadSuccess"; exports.SHOW_ERROR_MESSAGE = "brackets.showErrorMessage"; exports.NOTIFY_DOWNLOAD_FAILURE = "brackets.notifyDownloadFailure"; exports.NOTIFY_SAFE_TO_DOWNLOAD = "brackets.notifySafeToDownload"; exports.NOTIFY_INITIALIZATION_COMPLETE = "brackets.notifyinitializationComplete"; exports.NOTIFY_VALIDATION_STATUS = "brackets.notifyvalidationStatus"; exports.NOTIFY_INSTALLATION_STATUS = "brackets.notifyInstallationStatus"; exports.NODE_DOMAIN_INITIALIZED = "brackets.nodeDomainInitialized"; exports.REGISTER_BRACKETS_FUNCTIONS = "brackets.registerBracketsFunctions"; }); ================================================ FILE: src/extensions/default/AutoUpdate/StateHandler.js ================================================ /* * Copyright (c) 2018 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ define(function (require, exports, module) { "use strict"; var FileSystem = brackets.getModule("filesystem/FileSystem"), FileUtils = brackets.getModule("file/FileUtils"); var FILE_NOT_FOUND = 0, FILE_NOT_READ = 1, FILE_PARSE_EXCEPTION = 2, FILE_READ_FAIL = 3; /** * @constructor * Creates a StateHandler object for a JSON file. It maintains the following : * path - path to the JSON file, * state - parsed content of the file * @param {string} path - path to the JSON file */ var StateHandler = function (path) { this.path = path; this.state = null; }; /** * Checks if the file exists * @returns {$.Deferred} - a jquery deferred promise, * that is resolved with existence or non-existence * of json file. */ StateHandler.prototype.exists = function () { var result = $.Deferred(), _file = FileSystem.getFileForPath(this.path); _file.exists(function (err, exists) { if (err) { result.reject(); } else if (exists) { result.resolve(); } else { result.reject(); } }); return result.promise(); }; /** * Parses the JSON file, and maintains a state for the parsed data * @returns {$.Deferred} - a jquery deferred promise, * that is resolved with a parsing success or failure */ StateHandler.prototype.parse = function () { var result = $.Deferred(), _file = FileSystem.getFileForPath(this.path); var self = this; this.exists() .done(function () { FileUtils.readAsText(_file) .done(function (text) { try { if (text) { self.state = JSON.parse(text); result.resolve(); } else { result.reject(FILE_READ_FAIL); } } catch (error) { result.reject(FILE_PARSE_EXCEPTION); } }) .fail(function () { result.reject(FILE_NOT_READ); }); }) .fail(function () { result.reject(FILE_NOT_FOUND); }); return result.promise(); }; /** * Sets the value of a key in a json file. * @param {string} key - key for which the value is to be set * @param {string} value - the value to be set for the given key * @returns {$.Deferred} - a jquery deferred promise, that is resolved with a write success or failure */ StateHandler.prototype.set = function (key, value) { this.state = this.state || {}; this.state[key] = value; return this.write(true); }; /** * Gets the value for a given key, from the in-memory state maintained for a json file. * @param {string} key - key for which value is to be retrieved * @returns {string} value for the given key */ StateHandler.prototype.get = function (key) { var retval = null; if (this.state && this.state[key] !== undefined) { retval = this.state[key]; } return retval; }; /** * Reads the value of a key from a json file. * @param {string} key - key for which the value is to be read * @returns {$.Deferred} - a jquery deferred promise, that is resolved * with the read value or read failure. */ StateHandler.prototype.refresh = function () { var result = $.Deferred(), self = this; self.parse() .done(function () { if (self.state) { result.resolve(); } else { result.reject(); console.warn("AutoUpdate : updateHelper.json could not be read"); } }) .fail(function (error) { result.reject(); console.error("AutoUpdate : updateHelper.json could not be parsed", error); }); return result.promise(); }; /** * Performs the write of JSON object to a file. * @param {string} filepath - path to JSON file * @param {object} json - JSON object to write * @returns {$.Deferred} - a jquery deferred promise, * that is resolved with the write success or failure */ function _write(filePath, json) { var result = $.Deferred(), _file = FileSystem.getFileForPath(filePath); if (_file) { var content = JSON.stringify(json); FileUtils.writeText(_file, content, true) .done(function () { result.resolve(); }) .fail(function (err) { result.reject(); }); } else { result.reject(); } return result.promise(); } /** * Writes content into a json file * @param {boolean} overwrite - true if file is to be overwritten, false otherwise * @param {object} [content=this.state] - content to be written into the json file. * @returns {$.Deferred} - a jquery deferred promise, that is resolved with a write success or failure */ StateHandler.prototype.write = function (overwrite) { var result = $.Deferred(), self = this; function writePromise(path, contentToWrite) { _write(path, contentToWrite) .done(function () { result.resolve(); }) .fail(function (err) { result.reject(); }); } var content = self.state; if (overwrite) { writePromise(self.path, content); } else { //check for existence self.exists() .fail(function () { writePromise(self.path, content); }).done(function () { result.reject(); }); } return result.promise(); }; /** * Resets the content of the in-memory state */ StateHandler.prototype.reset = function () { this.state = null; }; exports.StateHandler = StateHandler; exports.MessageKeys = { FILE_NOT_FOUND: FILE_NOT_FOUND, FILE_NOT_READ: FILE_NOT_READ, FILE_PARSE_EXCEPTION: FILE_PARSE_EXCEPTION, FILE_READ_FAIL: FILE_READ_FAIL }; }); ================================================ FILE: src/extensions/default/AutoUpdate/UpdateInfoBar.js ================================================ /* * Copyright (c) 2018 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ define(function (require, exports, module) { "use strict"; var MainViewManager = brackets.getModule("view/MainViewManager"), Mustache = brackets.getModule("thirdparty/mustache/mustache"), EventDispatcher = brackets.getModule("utils/EventDispatcher"), UpdateBarHtml = require("text!htmlContent/updateBar.html"), Strings = brackets.getModule("strings"), _ = brackets.getModule("thirdparty/lodash"); EventDispatcher.makeEventDispatcher(exports); /** Event triggered when Restart button is clicked on the update bar */ var RESTART_BTN_CLICKED = "restartBtnClicked"; /** Event triggered when Later button is clicked on the update bar */ var LATER_BTN_CLICKED = "laterBtnClicked"; // Key handlers for buttons in UI var SPACE_KEY = 32, // keycode for space key ESC_KEY = 27; // keycode for escape key /** * Generates the json to be used by Mustache for rendering * @param {object} msgObj - json object containing message information to be displayed * @returns {object} - the generated json object */ function generateJsonForMustache(msgObj) { var msgJsonObj = {}; if (msgObj.type) { msgJsonObj.type = "'" + msgObj.type + "'"; } msgJsonObj.title = msgObj.title; msgJsonObj.description = msgObj.description; if (msgObj.needButtons) { msgJsonObj.buttons = [{ "id": "restart", "value": Strings.RESTART_BUTTON, "tIndex": "'0'" }, { "id": "later", "value": Strings.LATER_BUTTON, "tIndex": "'0'" }]; msgJsonObj.needButtons = msgObj.needButtons; } return msgJsonObj; } /** * Removes and cleans up the update bar from DOM */ function cleanUpdateBar() { var $updateBar = $('#update-bar'); if ($updateBar.length > 0) { $updateBar.remove(); } $(window.document).off("keydown.AutoUpdate"); $(window).off('resize.AutoUpdateBar'); } /** * Displays the Update Bar UI * @param {object} msgObj - json object containing message info to be displayed * */ function showUpdateBar(msgObj) { var jsonToMustache = generateJsonForMustache(msgObj), $updateBarElement = $(Mustache.render(UpdateBarHtml, jsonToMustache)); cleanUpdateBar(); //Remove an already existing update bar, if any $updateBarElement.prependTo(".content"); var $updateBar = $('#update-bar'), $updateContent = $updateBar.find('#update-content'), $contentContainer = $updateBar.find('#content-container'), $buttonContainer = $updateBar.find('#button-container'), $iconContainer = $updateBar.find('#icon-container'), $closeIconContainer = $updateBar.find('#close-icon-container'), $heading = $updateBar.find('#heading'), $description = $updateBar.find('#description'), $restart = $updateBar.find('#update-btn-restart'), $later = $updateBar.find('#update-btn-later'), $closeIcon = $updateBar.find('#close-icon'); if ($updateContent.length > 0) { if ($updateContent[0].scrollWidth > $updateContent.innerWidth()) { //Text has over-flown, show the update content as tooltip message if ($contentContainer.length > 0 && $heading.length > 0 && $description.length > 0) { $contentContainer.attr("title", $heading.text() + $description.text()); } } } // Content Container Width between Icon Container and Button Container or Close Icon Container // will be assigned when window will be rezied. var resizeContentContainer = function () { if($updateContent.length > 0 && $contentContainer.length > 0 && $updateBar.length > 0) { var newWidth = $updateBar.outerWidth() - 38; if($buttonContainer.length > 0) { newWidth = newWidth- $buttonContainer.outerWidth(); } if($iconContainer.length > 0) { newWidth = newWidth - $iconContainer.outerWidth(); } if($closeIconContainer.length > 0) { newWidth = newWidth - $closeIconContainer.outerWidth(); } $contentContainer.css({ "maxWidth": newWidth }); } }; resizeContentContainer(); $(window).on('resize.AutoUpdateBar', _.debounce(resizeContentContainer, 150)); //Event handlers on the Update Bar // Click and key handlers on Restart button if ($restart.length > 0) { $restart.click(function () { cleanUpdateBar(); exports.trigger(exports.RESTART_BTN_CLICKED); }); $restart.keyup(function (event) { if (event.which === SPACE_KEY) { $restart.trigger('click'); } }); } // Click and key handlers on Later button if ($later.length > 0) { $later.click(function () { cleanUpdateBar(); MainViewManager.focusActivePane(); exports.trigger(exports.LATER_BTN_CLICKED); }); $later.keyup(function (event) { if (event.which === SPACE_KEY) { $later.trigger('click'); } }); } // Click and key handlers on Close button if ($closeIcon.length > 0) { $closeIcon.click(function () { cleanUpdateBar(); MainViewManager.focusActivePane(); }); $closeIcon.keyup(function (event) { if (event.which === SPACE_KEY) { $closeIcon.trigger('click'); } }); } $(window.document).on("keydown.AutoUpdate", function (event) { var code = event.which; if (code === ESC_KEY) { // Keyboard input of Esc key on Update Bar dismisses and removes the bar cleanUpdateBar(); MainViewManager.focusActivePane(); event.stopImmediatePropagation(); } }); } exports.showUpdateBar = showUpdateBar; exports.RESTART_BTN_CLICKED = RESTART_BTN_CLICKED; exports.LATER_BTN_CLICKED = LATER_BTN_CLICKED; }); ================================================ FILE: src/extensions/default/AutoUpdate/UpdateStatus.js ================================================ /* * Copyright (c) 2018 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ define(function (require, exports, module) { "use strict"; var UpdateStatusHtml = require("text!htmlContent/updateStatus.html"), ExtensionUtils = brackets.getModule("utils/ExtensionUtils"), Mustache = brackets.getModule("thirdparty/mustache/mustache"), Strings = brackets.getModule("strings"), StringUtils = brackets.getModule("utils/StringUtils"); ExtensionUtils.loadStyleSheet(module, "styles/styles.css"); /** * Cleans up status information from Status Bar */ function cleanUpdateStatus() { $('#update-status').remove(); } /** * Displays the status information on Status Bar * @param {string} id - the id of string to display */ function showUpdateStatus(id) { cleanUpdateStatus(); var $updateStatus = $(Mustache.render(UpdateStatusHtml, {"Strings": Strings})); $updateStatus.appendTo('#status-bar'); if(id === "initial-download") { var valStr = StringUtils.format(Strings.NUMBER_WITH_PERCENTAGE, 0); $('#update-status #' + id + ' #' + 'percent').text(valStr); } $('#update-status #' + id).show(); } /** * Modifies the status information on Status Bar * @param {object} statusObj - json containing status info - { * target - id of string to modify, * spans - array of objects of type - { * id - id of span for modifiable substring, * val - the new value to modifiable substring } * } */ function modifyUpdateStatus(statusObj) { statusObj.spans.forEach(function (span) { var valStr = span.val; if(span.id === "percent") { valStr = StringUtils.format(Strings.NUMBER_WITH_PERCENTAGE, span.val.split('%')[0]); } $('#update-status #' + statusObj.target + ' #' + span.id).text(valStr); }); } /** * Displays the progress bar on Status bar, while the download is in progress * @param {object} statusObj - json containing status info - { * target - id of string to modify, * spans - array of objects of type - { * id - id of span for modifiable substring, * val - the new value to modifiable substring } * } */ function displayProgress(statusObj) { statusObj.spans.forEach(function (span) { if (span.id === 'percent') { var bgColor = $('#status-bar').css('backgroundColor'), bgval = 'linear-gradient(to right, #1474BF ' + span.val + ', ' + bgColor + ' 0%'; $('#update-status').css('background', bgval); } }); } exports.showUpdateStatus = showUpdateStatus; exports.modifyUpdateStatus = modifyUpdateStatus; exports.cleanUpdateStatus = cleanUpdateStatus; exports.displayProgress = displayProgress; }); ================================================ FILE: src/extensions/default/AutoUpdate/htmlContent/updateBar.html ================================================

      {{title}}  {{{description}}}

      {{#needButtons}}
      {{#buttons}} {{/buttons}}
      {{/needButtons}} {{^buttons}}
      {{/buttons}}
      ================================================ FILE: src/extensions/default/AutoUpdate/htmlContent/updateStatus.html ================================================

      {{Strings.INITIAL_DOWNLOAD}}

      {{Strings.RETRY_DOWNLOAD}}1/5

      {{Strings.VALIDATING_INSTALLER}}

      ================================================ FILE: src/extensions/default/AutoUpdate/main.js ================================================ /* * Copyright (c) 2018 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /* eslint-disable indent */ /* eslint-disable max-len */ define(function (require, exports, module) { "use strict"; var CommandManager = brackets.getModule("command/CommandManager"), ProjectManager = brackets.getModule("project/ProjectManager"), Commands = brackets.getModule("command/Commands"), AppInit = brackets.getModule("utils/AppInit"), UpdateNotification = brackets.getModule("utils/UpdateNotification"), DocumentCommandHandlers = brackets.getModule("document/DocumentCommandHandlers"), NodeDomain = brackets.getModule("utils/NodeDomain"), FileSystem = brackets.getModule("filesystem/FileSystem"), PreferencesManager = brackets.getModule("preferences/PreferencesManager"), FileUtils = brackets.getModule("file/FileUtils"), Strings = brackets.getModule("strings"), HealthLogger = brackets.getModule("utils/HealthLogger"), StateHandlerModule = require("StateHandler"), MessageIds = require("MessageIds"), UpdateStatus = require("UpdateStatus"), UpdateInfoBar = require("UpdateInfoBar"); var _modulePath = FileUtils.getNativeModuleDirectoryPath(module), _nodePath = "node/AutoUpdateDomain", _domainPath = [_modulePath, _nodePath].join("/"), updateDomain, domainID; var appSupportDirectory = brackets.app.getApplicationSupportDirectory(), updateDir = appSupportDirectory + '/updateTemp', updateJsonPath = updateDir + '/' + 'updateHelper.json'; var StateHandler = StateHandlerModule.StateHandler, StateHandlerMessages = StateHandlerModule.MessageKeys; var updateJsonHandler; var MAX_DOWNLOAD_ATTEMPTS = 6, downloadAttemptsRemaining; // Below Strings are to identify an AutoUpdate Event. var autoUpdateEventNames = { AUTOUPDATE_DOWNLOAD_START: "DownloadStarted", AUTOUPDATE_DOWNLOAD_COMPLETED: "DownloadCompleted", AUTOUPDATE_DOWNLOAD_FAILED: "DownloadFailed", AUTOUPDATE_DOWNLOAD_COMPLETE_USER_CLICK_RESTART: "DownloadCompletedAndUserClickedRestart", AUTOUPDATE_DOWNLOAD_COMPLETE_USER_CLICK_LATER: "DownloadCompletedAndUserClickedLater", AUTOUPDATE_DOWNLOADCOMPLETE_UPDATE_BAR_RENDERED: "DownloadCompleteUpdateBarRendered", AUTOUPDATE_INSTALLATION_FAILED: "InstallationFailed", AUTOUPDATE_INSTALLATION_SUCCESS: "InstallationSuccess", AUTOUPDATE_CLEANUP_FAILED: "AutoUpdateCleanUpFailed" }; // function map for brackets functions var functionMap = {}; var _updateParams; //Namespacing the event var APP_QUIT_CANCELLED = DocumentCommandHandlers.APP_QUIT_CANCELLED + ".auto-update"; var _nodeErrorMessages = { UPDATEDIR_READ_FAILED: 0, UPDATEDIR_CLEAN_FAILED: 1, CHECKSUM_DID_NOT_MATCH: 2, INSTALLER_NOT_FOUND: 3, DOWNLOAD_ERROR: 4, NETWORK_SLOW_OR_DISCONNECTED: 5 }; var updateProgressKey = "autoUpdateInProgress", isAutoUpdateInitiated = false; /** * Checks if an auto update session is currently in progress * @returns {boolean} - true if an auto update session is currently in progress, false otherwise */ function checkIfAnotherSessionInProgress() { var result = $.Deferred(); if(updateJsonHandler) { var state = updateJsonHandler.state; updateJsonHandler.refresh() .done(function() { var val = updateJsonHandler.get(updateProgressKey); if(val !== null) { result.resolve(val); } else { result.reject(); } }) .fail(function() { updateJsonHandler.state = state; result.reject(); }); } return result.promise(); } /** * Checks if auto update preference is enabled or disabled * @private * @returns {boolean} - true if preference enabled, false otherwise */ function _isAutoUpdateEnabled() { return (PreferencesManager.get("autoUpdate.AutoUpdate") !== false); } /** * Receives messages from node * @param {object} event - event received from node * @param {object} msgObj - json containing - { * fn - function to execute on Brackets side * args - arguments to the above function * requester - ID of the current requester domain */ function receiveMessageFromNode(event, msgObj) { if (functionMap[msgObj.fn]) { if(domainID === msgObj.requester) { functionMap[msgObj.fn].apply(null, msgObj.args); } } } /* * Checks if Brackets version got updated * @returns {boolean} true if version updated, false otherwise */ function checkIfVersionUpdated() { var latestBuildNumber = updateJsonHandler.get("latestBuildNumber"), currentBuildNumber = Number(/-([0-9]+)/.exec(brackets.metadata.version)[1]); return latestBuildNumber === currentBuildNumber; } /** * Gets the arguments to a function in an array * @param {object} args - the arguments object * @returns {Array} - array of actual arguments */ function getFunctionArgs(args) { if (args.length > 1) { var fnArgs = new Array(args.length - 1), i; for (i = 1; i < args.length; ++i) { fnArgs[i - 1] = args[i]; } return fnArgs; } return []; } /** * Posts messages to node * @param {string} messageId - Message to be passed */ function postMessageToNode(messageId) { var msg = { fn: messageId, args: getFunctionArgs(arguments), requester: domainID }; updateDomain.exec('data', msg); } /** * Checks Install failure scenarios */ function checkInstallationStatus() { var searchParams = { "updateDir": updateDir, "installErrorStr": ["ERROR:"], "bracketsErrorStr": ["ERROR:"], "encoding": "utf8" }, // Below are the possible Windows Installer error strings, which will be searched for in the installer logs to track failures. winInstallErrorStrArr = [ "Installation success or error status", "Reconfiguration success or error status" ]; if (brackets.platform === "win") { searchParams.installErrorStr = winInstallErrorStrArr; searchParams.encoding = "utf16le"; } postMessageToNode(MessageIds.CHECK_INSTALLER_STATUS, searchParams); } /** * Checks and handles the update success and failure scenarios */ function checkUpdateStatus() { var filesToCache = ['.logs'], downloadCompleted = updateJsonHandler.get("downloadCompleted"), updateInitiatedInPrevSession = updateJsonHandler.get("updateInitiatedInPrevSession"); if (downloadCompleted && updateInitiatedInPrevSession) { var isNewVersion = checkIfVersionUpdated(); updateJsonHandler.reset(); if (isNewVersion) { // We get here if the update was successful UpdateInfoBar.showUpdateBar({ type: "success", title: Strings.UPDATE_SUCCESSFUL, description: "" }); HealthLogger.sendAnalyticsData( autoUpdateEventNames.AUTOUPDATE_INSTALLATION_SUCCESS, "autoUpdate", "install", "complete", "" ); } else { // We get here if the update started but failed checkInstallationStatus(); UpdateInfoBar.showUpdateBar({ type: "error", title: Strings.UPDATE_FAILED, description: Strings.GO_TO_SITE }); } } else if (downloadCompleted && !updateInitiatedInPrevSession) { // We get here if the download was complete and user selected UpdateLater if (brackets.platform === "mac") { filesToCache = ['.dmg', '.json']; } else if (brackets.platform === "win") { filesToCache = ['.msi', '.json']; } } postMessageToNode(MessageIds.PERFORM_CLEANUP, filesToCache); } /** * Send Installer Error Code to Analytics Server */ function handleInstallationStatus(statusObj) { var errorCode = "", errorline = statusObj.installError; if (errorline) { errorCode = errorline.substr(errorline.lastIndexOf(':') + 2, errorline.length); } HealthLogger.sendAnalyticsData( autoUpdateEventNames.AUTOUPDATE_INSTALLATION_FAILED, "autoUpdate", "install", "fail", errorCode ); } /** * Initializes the state of parsed content from updateHelper.json * returns Promise Object Which is resolved when parsing is success * and rejected if parsing is failed. */ function initState() { var result = $.Deferred(); updateJsonHandler.parse() .done(function() { result.resolve(); }) .fail(function (code) { var logMsg; switch (code) { case StateHandlerMessages.FILE_NOT_FOUND: logMsg = "AutoUpdate : updateHelper.json cannot be parsed, does not exist"; break; case StateHandlerMessages.FILE_NOT_READ: logMsg = "AutoUpdate : updateHelper.json could not be read"; break; case StateHandlerMessages.FILE_PARSE_EXCEPTION: logMsg = "AutoUpdate : updateHelper.json could not be parsed, exception encountered"; break; case StateHandlerMessages.FILE_READ_FAIL: logMsg = "AutoUpdate : updateHelper.json could not be parsed"; break; } console.log(logMsg); result.reject(); }); return result.promise(); } /** * Sets up the Auto Update environment */ function setupAutoUpdate() { updateJsonHandler = new StateHandler(updateJsonPath); updateDomain.on('data', receiveMessageFromNode); updateDomain.exec('initNode', { messageIds: MessageIds, updateDir: updateDir, requester: domainID }); } /** * Initializes the state for AutoUpdate process * @returns {$.Deferred} - a jquery promise, * that is resolved with success or failure * of state initialization */ function initializeState() { var result = $.Deferred(); FileSystem.resolve(updateDir, function (err) { if (!err) { result.resolve(); } else { var directory = FileSystem.getDirectoryForPath(updateDir); directory.create(function (error) { if (error) { console.error('AutoUpdate : Error in creating update directory in Appdata'); result.reject(); } else { result.resolve(); } }); } }); return result.promise(); } /** * Handles the auto update event, which is triggered when user clicks GetItNow in UpdateNotification dialog * @param {object} updateParams - json object containing update information { * installerName - name of the installer * downloadURL - download URL * latestBuildNumber - build number * checksum - checksum } */ function initiateAutoUpdate(updateParams) { _updateParams = updateParams; downloadAttemptsRemaining = MAX_DOWNLOAD_ATTEMPTS; initializeState() .done(function () { var setUpdateInProgress = function() { var initNodeFn = function () { isAutoUpdateInitiated = true; postMessageToNode(MessageIds.INITIALIZE_STATE, _updateParams); }; if (updateJsonHandler.get('latestBuildNumber') !== _updateParams.latestBuildNumber) { setUpdateStateInJSON('latestBuildNumber', _updateParams.latestBuildNumber) .done(initNodeFn); } else { initNodeFn(); } }; checkIfAnotherSessionInProgress() .done(function(inProgress) { if(inProgress) { UpdateInfoBar.showUpdateBar({ type: "error", title: Strings.AUTOUPDATE_ERROR, description: Strings.AUTOUPDATE_IN_PROGRESS }); } else { setUpdateStateInJSON("autoUpdateInProgress", true) .done(setUpdateInProgress); } }) .fail(function() { setUpdateStateInJSON("autoUpdateInProgress", true) .done(setUpdateInProgress); }); }) .fail(function () { UpdateInfoBar.showUpdateBar({ type: "error", title: Strings.INITIALISATION_FAILED, description: "" }); }); } /** * Typical signature of an update entry, with the most frequently used keys * @typedef {Object} Update~Entry * @property {Number} buildNumber - The build number for the update * @property {string} versionString - Version for the update * @property {string} releaseNotesURL - URL for release notes for the update * @property {array} newFeatures - Array of new features in the update * @property {boolean} prerelease - Boolean to distinguish prerelease from a stable release * @property {Object} platforms - JSON object, containing asset info for the update, for each platform * Asset info for each platform consists of : * @property {string} checksum - checksum of the asset * @property {string} downloadURL - download URL of the asset * */ /** * Handles and processes the update info, required for app auto update * @private * @param {Array} updates - array of {...Update~Entry} update entries */ function _updateProcessHandler(updates) { if (!updates) { console.warn("AutoUpdate : updates information not available."); return; } var OS = brackets.getPlatformInfo(), checksum, downloadURL, installerName, platforms, latestUpdate; latestUpdate = updates[0]; platforms = latestUpdate ? latestUpdate.platforms : null; if (platforms && platforms[OS]) { //If no checksum field is present then we're setting it to 0, just as a safety check, // although ideally this situation should never occur in releases post its introduction. checksum = platforms[OS].checksum ? platforms[OS].checksum : 0, downloadURL = platforms[OS].downloadURL ? platforms[OS].downloadURL : "", installerName = downloadURL ? downloadURL.split("/").pop() : ""; } else { // Update not present for current platform return false; } if (!checksum || !downloadURL || !installerName) { console.warn("AutoUpdate : asset information incorrect for the update"); return false; } var updateParams = { downloadURL: downloadURL, installerName: installerName, latestBuildNumber: latestUpdate.buildNumber, checksum: checksum }; //Initiate the auto update, with update params initiateAutoUpdate(updateParams); //Send a truthy value to ensure caller is informed about successful initialization of auto-update return true; } /** * Unregisters the App Quit event handler */ function resetAppQuitHandler() { DocumentCommandHandlers.off(APP_QUIT_CANCELLED); } /** * Unsets the Auto Update environment */ function unsetAutoUpdate() { updateJsonHandler = null; updateDomain.off('data'); resetAppQuitHandler(); } /** * Defines preference to enable/disable Auto Update */ function setupAutoUpdatePreference() { PreferencesManager.definePreference("autoUpdate.AutoUpdate", "boolean", true, { description: Strings.DESCRIPTION_AUTO_UPDATE }); // Set or unset the auto update, based on preference state change PreferencesManager.on("change", "autoUpdate.AutoUpdate", function () { if (_isAutoUpdateEnabled()) { setupAutoUpdate(); UpdateNotification.registerUpdateHandler(_updateProcessHandler); } else { unsetAutoUpdate(); UpdateNotification.resetToDefaultUpdateHandler(); } }); } /** * Creates the Node Domain for Auto Update */ function setupAutoUpdateDomain() { updateDomain = new NodeDomain("AutoUpdate", _domainPath); } /** * Overriding the appReady for Auto update */ AppInit.appReady(function () { // Auto Update is supported on Win and Mac, as of now if (brackets.platform === "linux" || !(brackets.app.setUpdateParams)) { return; } setupAutoUpdateDomain(); //Bail out if update domain could not be created if (!updateDomain) { return; } // Check if the update domain is properly initialised updateDomain.promise() .done(function () { setupAutoUpdatePreference(); if (_isAutoUpdateEnabled()) { domainID = (new Date()).getTime().toString(); setupAutoUpdate(); UpdateNotification.registerUpdateHandler(_updateProcessHandler); } }) .fail(function (err) { console.error("AutoUpdate : node domain could not be initialized."); return; }); }); /** * Enables/disables the state of "Auto Update In Progress" in UpdateHandler.json */ function nodeDomainInitialized(reset) { initState() .done(function () { var inProgress = updateJsonHandler.get(updateProgressKey); if (inProgress && reset) { setUpdateStateInJSON(updateProgressKey, !reset) .always(checkUpdateStatus); } else if (!inProgress) { checkUpdateStatus(); } }); } /** * Enables/disables the state of "Check For Updates" menu entry under Help Menu */ function enableCheckForUpdateEntry(enable) { var cfuCommand = CommandManager.get(Commands.HELP_CHECK_FOR_UPDATE); cfuCommand.setEnabled(enable); UpdateNotification.enableUpdateNotificationIcon(enable); } /** * Checks if it is the first iteration of download * @returns {boolean} - true if first iteration, false if it is a retrial of download */ function isFirstIterationDownload() { return (downloadAttemptsRemaining === MAX_DOWNLOAD_ATTEMPTS); } /** * Resets the update state in updatehelper.json in case of failure, * and logs an error with the message * @param {string} message - the message to be logged onto console */ function resetStateInFailure(message) { updateJsonHandler.reset(); UpdateInfoBar.showUpdateBar({ type: "error", title: Strings.UPDATE_FAILED, description: "" }); enableCheckForUpdateEntry(true); console.error(message); } /** * Sets the update state in updateHelper.json in Appdata * @param {string} key - key to be set * @param {string} value - value to be set * @returns {$.Deferred} - a jquery promise, that is resolved with * success or failure of state update in json file */ function setUpdateStateInJSON(key, value) { var result = $.Deferred(); updateJsonHandler.set(key, value) .done(function () { result.resolve(); }) .fail(function () { resetStateInFailure("AutoUpdate : Could not modify updatehelper.json"); result.reject(); }); return result.promise(); } /** * Handles a safe download of the latest installer, * safety is ensured by cleaning up any pre-existing installers * from update directory before beginning a fresh download */ function handleSafeToDownload() { var downloadFn = function () { if (isFirstIterationDownload()) { // For the first iteration of download, show download //status info in Status bar, and pass download to node UpdateStatus.showUpdateStatus("initial-download"); postMessageToNode(MessageIds.DOWNLOAD_INSTALLER, true); } else { /* For the retry iterations of download, modify the download status info in Status bar, and pass download to node */ var attempt = (MAX_DOWNLOAD_ATTEMPTS - downloadAttemptsRemaining); if (attempt > 1) { var info = attempt.toString() + "/5"; var status = { target: "retry-download", spans: [{ id: "attempt", val: info }] }; UpdateStatus.modifyUpdateStatus(status); } else { UpdateStatus.showUpdateStatus("retry-download"); } postMessageToNode(MessageIds.DOWNLOAD_INSTALLER, false); } --downloadAttemptsRemaining; }; if(!isAutoUpdateInitiated) { isAutoUpdateInitiated = true; updateJsonHandler.refresh() .done(function() { setUpdateStateInJSON('downloadCompleted', false) .done(downloadFn); }); } else { setUpdateStateInJSON('downloadCompleted', false) .done(downloadFn); } } /** * Checks if there is an active internet connection available * @returns {boolean} - true if online, false otherwise */ function checkIfOnline() { return window.navigator.onLine; } /** * Attempts a download of the latest installer, while cleaning up any existing downloaded installers */ function attemptToDownload() { if (checkIfOnline()) { postMessageToNode(MessageIds.PERFORM_CLEANUP, ['.json'], true); } else { enableCheckForUpdateEntry(true); UpdateStatus.cleanUpdateStatus(); HealthLogger.sendAnalyticsData( autoUpdateEventNames.AUTOUPDATE_DOWNLOAD_FAILED, "autoUpdate", "download", "fail", "No Internet connection available." ); UpdateInfoBar.showUpdateBar({ type: "warning", title: Strings.DOWNLOAD_FAILED, description: Strings.INTERNET_UNAVAILABLE }); setUpdateStateInJSON("autoUpdateInProgress", false); } } /** * Validates the checksum of a file against a given checksum * @param {object} params - json containing { * filePath - path to the file, * expectedChecksum - the checksum to validate against } */ function validateChecksum(params) { postMessageToNode(MessageIds.VALIDATE_INSTALLER, params); } /** * Gets the latest installer, by either downloading a new one or fetching the cached download. */ function getLatestInstaller() { var downloadCompleted = updateJsonHandler.get('downloadCompleted'); if (!downloadCompleted) { attemptToDownload(); } else { validateChecksum(); } } /** * Handles the show status information callback from Node. * It modifies the info displayed on Status bar. * @param {object} statusObj - json containing status info { * target - id of string to display, * spans - Array containing json objects of type - { * id - span id, * val - string to fill the span element with } * } */ function showStatusInfo(statusObj) { if (statusObj.target === "initial-download") { HealthLogger.sendAnalyticsData( autoUpdateEventNames.AUTOUPDATE_DOWNLOAD_START, "autoUpdate", "download", "started", "" ); UpdateStatus.modifyUpdateStatus(statusObj); } UpdateStatus.displayProgress(statusObj); } /** * Handles the error messages from Node, in a popup displayed to the user. * @param {string} message - error string */ function showErrorMessage(message) { var analyticsDescriptionMessage = ""; switch (message) { case _nodeErrorMessages.UPDATEDIR_READ_FAILED: analyticsDescriptionMessage = "Update directory could not be read."; break; case _nodeErrorMessages.UPDATEDIR_CLEAN_FAILED: analyticsDescriptionMessage = "Update directory could not be cleaned."; break; } console.log("AutoUpdate : Clean-up failed! Reason : " + analyticsDescriptionMessage + ".\n"); HealthLogger.sendAnalyticsData( autoUpdateEventNames.AUTOUPDATE_CLEANUP_FAILED, "autoUpdate", "cleanUp", "fail", analyticsDescriptionMessage ); } /** * Handles the Cancel button click by user in * Unsaved changes prompt, which would come up if user * has dirty files and he/she clicked UpdateNow */ function dirtyFileSaveCancelled() { UpdateInfoBar.showUpdateBar({ type: "warning", title: Strings.WARNING_TYPE, description: Strings.UPDATE_ON_NEXT_LAUNCH }); } /** * Registers the App Quit event handler, in case of dirty * file save cancelled scenario, while Auto Update is scheduled to run on quit */ function setAppQuitHandler() { resetAppQuitHandler(); DocumentCommandHandlers.on(APP_QUIT_CANCELLED, dirtyFileSaveCancelled); } /** * Initiates the update process, when user clicks UpdateNow in the update popup * @param {string} formattedInstallerPath - formatted path to the latest installer * @param {string} formattedLogFilePath - formatted path to the installer log file * @param {string} installStatusFilePath - path to the install status log file */ function initiateUpdateProcess(formattedInstallerPath, formattedLogFilePath, installStatusFilePath) { // Get additional update parameters on Mac : installDir, appName, and updateDir function getAdditionalParams() { var retval = {}; var installDir = FileUtils.getNativeBracketsDirectoryPath(); if (installDir) { var appPath = installDir.split("/Contents/www")[0]; installDir = appPath.substr(0, appPath.lastIndexOf('/')); var appName = appPath.substr(appPath.lastIndexOf('/') + 1); retval = { installDir: installDir, appName: appName, updateDir: updateDir }; } return retval; } // Update function, to carry out app update var updateFn = function () { var infoObj = { installerPath: formattedInstallerPath, logFilePath: formattedLogFilePath, installStatusFilePath: installStatusFilePath }; if (brackets.platform === "mac") { var additionalParams = getAdditionalParams(), key; for (key in additionalParams) { if (additionalParams.hasOwnProperty(key)) { infoObj[key] = additionalParams[key]; } } } // Set update parameters for app update if (brackets.app.setUpdateParams) { brackets.app.setUpdateParams(JSON.stringify(infoObj), function (err) { if (err) { resetStateInFailure("AutoUpdate : Update parameters could not be set for the installer. Error encountered: " + err); } else { setAppQuitHandler(); CommandManager.execute(Commands.FILE_QUIT); } }); } else { resetStateInFailure("AutoUpdate : setUpdateParams could not be found in shell"); } }; setUpdateStateInJSON('updateInitiatedInPrevSession', true) .done(updateFn); } /** * Detaches the Update Bar Buttons event handlers */ function detachUpdateBarBtnHandlers() { UpdateInfoBar.off(UpdateInfoBar.RESTART_BTN_CLICKED); UpdateInfoBar.off(UpdateInfoBar.LATER_BTN_CLICKED); } /** * Handles the installer validation callback from Node * @param {object} statusObj - json containing - { * valid - (boolean)true for a valid installer, false otherwise, * installerPath, logFilePath, * installStatusFilePath - for a valid installer, * err - for an invalid installer } */ function handleValidationStatus(statusObj) { enableCheckForUpdateEntry(true); UpdateStatus.cleanUpdateStatus(); if (statusObj.valid) { // Installer is validated successfully var statusValidFn = function () { // Restart button click handler var restartBtnClicked = function () { HealthLogger.sendAnalyticsData( autoUpdateEventNames.AUTOUPDATE_DOWNLOAD_COMPLETE_USER_CLICK_RESTART, "autoUpdate", "installNotification", "installNow ", "click" ); detachUpdateBarBtnHandlers(); initiateUpdateProcess(statusObj.installerPath, statusObj.logFilePath, statusObj.installStatusFilePath); }; // Later button click handler var laterBtnClicked = function () { HealthLogger.sendAnalyticsData( autoUpdateEventNames.AUTOUPDATE_DOWNLOAD_COMPLETE_USER_CLICK_LATER, "autoUpdate", "installNotification", "cancel", "click" ); detachUpdateBarBtnHandlers(); setUpdateStateInJSON('updateInitiatedInPrevSession', false); }; //attaching UpdateBar handlers UpdateInfoBar.on(UpdateInfoBar.RESTART_BTN_CLICKED, restartBtnClicked); UpdateInfoBar.on(UpdateInfoBar.LATER_BTN_CLICKED, laterBtnClicked); UpdateInfoBar.showUpdateBar({ title: Strings.DOWNLOAD_COMPLETE, description: Strings.CLICK_RESTART_TO_UPDATE, needButtons: true }); setUpdateStateInJSON("autoUpdateInProgress", false); HealthLogger.sendAnalyticsData( autoUpdateEventNames.AUTOUPDATE_DOWNLOADCOMPLETE_UPDATE_BAR_RENDERED, "autoUpdate", "installNotification", "render", "" ); }; if(!isAutoUpdateInitiated) { isAutoUpdateInitiated = true; updateJsonHandler.refresh() .done(function() { setUpdateStateInJSON('downloadCompleted', true) .done(statusValidFn); }); } else { setUpdateStateInJSON('downloadCompleted', true) .done(statusValidFn); } } else { // Installer validation failed if (updateJsonHandler.get("downloadCompleted")) { // If this was a cached download, retry downloading updateJsonHandler.reset(); var statusInvalidFn = function () { downloadAttemptsRemaining = MAX_DOWNLOAD_ATTEMPTS; getLatestInstaller(); }; setUpdateStateInJSON('downloadCompleted', false) .done(statusInvalidFn); } else { // If this is a new download, prompt the message on update bar var descriptionMessage = "", analyticsDescriptionMessage = ""; switch (statusObj.err) { case _nodeErrorMessages.CHECKSUM_DID_NOT_MATCH: descriptionMessage = Strings.CHECKSUM_DID_NOT_MATCH; analyticsDescriptionMessage = "Checksum didn't match."; break; case _nodeErrorMessages.INSTALLER_NOT_FOUND: descriptionMessage = Strings.INSTALLER_NOT_FOUND; analyticsDescriptionMessage = "Installer not found."; break; } HealthLogger.sendAnalyticsData( autoUpdateEventNames.AUTOUPDATE_DOWNLOAD_FAILED, "autoUpdate", "download", "fail", analyticsDescriptionMessage ); UpdateInfoBar.showUpdateBar({ type: "error", title: Strings.VALIDATION_FAILED, description: descriptionMessage }); setUpdateStateInJSON("autoUpdateInProgress", false); } } } /** * Handles the download failure callback from Node * @param {string} message - reason of download failure */ function handleDownloadFailure(message) { console.log("AutoUpdate : Download of latest installer failed in Attempt " + (MAX_DOWNLOAD_ATTEMPTS - downloadAttemptsRemaining) + ".\n Reason : " + message); if (downloadAttemptsRemaining) { // Retry the downloading attemptToDownload(); } else { // Download could not completed, all attempts exhausted enableCheckForUpdateEntry(true); UpdateStatus.cleanUpdateStatus(); var descriptionMessage = "", analyticsDescriptionMessage = ""; if (message === _nodeErrorMessages.DOWNLOAD_ERROR) { descriptionMessage = Strings.DOWNLOAD_ERROR; analyticsDescriptionMessage = "Error occurred while downloading."; } else if (message === _nodeErrorMessages.NETWORK_SLOW_OR_DISCONNECTED) { descriptionMessage = Strings.NETWORK_SLOW_OR_DISCONNECTED; analyticsDescriptionMessage = "Network is Disconnected or too slow."; } HealthLogger.sendAnalyticsData( autoUpdateEventNames.AUTOUPDATE_DOWNLOAD_FAILED, "autoUpdate", "download", "fail", analyticsDescriptionMessage ); UpdateInfoBar.showUpdateBar({ type: "error", title: Strings.DOWNLOAD_FAILED, description: descriptionMessage }); setUpdateStateInJSON("autoUpdateInProgress", false); } } /** * Handles the completion of node state initialization */ function handleInitializationComplete() { enableCheckForUpdateEntry(false); getLatestInstaller(); } /** * Handles Download completion callback from Node */ function handleDownloadSuccess() { HealthLogger.sendAnalyticsData( autoUpdateEventNames.AUTOUPDATE_DOWNLOAD_COMPLETED, "autoUpdate", "download", "complete", "" ); UpdateStatus.showUpdateStatus("validating-installer"); validateChecksum(); } function _handleAppClose() { postMessageToNode(MessageIds.REMOVE_FROM_REQUESTERS); } /** * Generates a map for brackets side functions */ function registerBracketsFunctions() { functionMap["brackets.notifyinitializationComplete"] = handleInitializationComplete; functionMap["brackets.showStatusInfo"] = showStatusInfo; functionMap["brackets.notifyDownloadSuccess"] = handleDownloadSuccess; functionMap["brackets.showErrorMessage"] = showErrorMessage; functionMap["brackets.notifyDownloadFailure"] = handleDownloadFailure; functionMap["brackets.notifySafeToDownload"] = handleSafeToDownload; functionMap["brackets.notifyvalidationStatus"] = handleValidationStatus; functionMap["brackets.notifyInstallationStatus"] = handleInstallationStatus; ProjectManager.on("beforeProjectClose beforeAppClose", _handleAppClose); } functionMap["brackets.nodeDomainInitialized"] = nodeDomainInitialized; functionMap["brackets.registerBracketsFunctions"] = registerBracketsFunctions; }); ================================================ FILE: src/extensions/default/AutoUpdate/node/AutoUpdateDomain.js ================================================ /* * Copyright (c) 2018 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /*global exports */ /*global process*/ (function () { "use strict"; var _domainManager; var request = require('request'), progress = require('request-progress'), path = require('path'), fs = require('fs-extra'), crypto = require('crypto'); // Current Date and Time needed for log filenames var curDate = Date.now().toString(); //AUTOUPDATE_PRERELEASE //Installer log file var logFile = curDate + 'update.logs', logFilePath; //Install status file var installStatusFile = curDate + 'installStatus.logs', installStatusFilePath; var updateDir, _updateParams; var MessageIds, installerPath; // function map for node functions var functionMap = {}; var nodeErrorMessages = { UPDATEDIR_READ_FAILED: 0, UPDATEDIR_CLEAN_FAILED: 1, CHECKSUM_DID_NOT_MATCH: 2, INSTALLER_NOT_FOUND: 3, DOWNLOAD_ERROR: 4, NETWORK_SLOW_OR_DISCONNECTED: 5 }; var requesters = {}, isNodeDomainInitialized = false; /** * Gets the arguments to a function in an array * @param {object} args - the arguments object * @returns {Array} - array of actual arguments */ function getFunctionArgs(args) { if (args.length > 2) { var fnArgs = new Array(args.length - 2), i; for (i = 2; i < args.length; ++i) { fnArgs[i - 2] = args[i]; } return fnArgs; } return []; } /** * Posts messages to brackets * @param {string} messageId - Message to be passed */ function postMessageToBrackets(messageId, requester) { if(!requesters[requester]) { for (var key in requesters) { requester = key; break; } } var msgObj = { fn: messageId, args: getFunctionArgs(arguments), requester: requester.toString() }; _domainManager.emitEvent('AutoUpdate', 'data', [msgObj]); } /** * Quotes and Converts a file path, to accommodate platform dependent paths * @param {string} qncPath - file path * @param {boolean} resolve - false if path is only to be quoted, true if both quoted and converted * @returns {string} quoted and converted file path */ function quoteAndConvert(qncPath, resolve) { if (resolve) { qncPath = path.resolve(qncPath); } return "\"" + qncPath + "\""; } /** * Validates the checksum of a file against a given checksum * @param {object} params - json containing { * filePath - path to the file, * expectedChecksum - the checksum to validate against } */ function validateChecksum(requester, params) { params = params || { filePath: installerPath, expectedChecksum: _updateParams.checksum }; var hash = crypto.createHash('sha256'), currentRequester = requester || ""; if (fs.existsSync(params.filePath)) { var stream = fs.createReadStream(params.filePath); stream.on('data', function (data) { hash.update(data); }); stream.on('end', function () { var calculatedChecksum = hash.digest('hex'), isValidChecksum = (params.expectedChecksum === calculatedChecksum), status; if (isValidChecksum) { if (process.platform === "darwin") { status = { valid: true, installerPath: installerPath, logFilePath: logFilePath, installStatusFilePath: installStatusFilePath }; } else if (process.platform === "win32") { status = { valid: true, installerPath: quoteAndConvert(installerPath, true), logFilePath: quoteAndConvert(logFilePath, true), installStatusFilePath: installStatusFilePath }; } } else { status = { valid: false, err: nodeErrorMessages.CHECKSUM_DID_NOT_MATCH }; } postMessageToBrackets(MessageIds.NOTIFY_VALIDATION_STATUS, currentRequester, status); }); } else { var status = { valid: false, err: nodeErrorMessages.INSTALLER_NOT_FOUND }; postMessageToBrackets(MessageIds.NOTIFY_VALIDATION_STATUS, currentRequester, status); } } /** * Parse the Installer log and search for a error strings * one it finds the line which has any of error String * it return that line and exit */ function parseInstallerLog(filepath, searchstring, encoding, callback) { var line = ""; var searchFn = function searchFn(str) { var arr = str.split('\n'), lineNum, pos; for (lineNum = arr.length - 1; lineNum >= 0; lineNum--) { var searchStrNum; for (searchStrNum = 0; searchStrNum < searchstring.length; searchStrNum++) { pos = arr[lineNum].search(searchstring[searchStrNum]); if (pos !== -1) { line = arr[lineNum]; break; } } if (pos !== -1) { break; } } callback(line); }; fs.readFile(filepath, {"encoding": encoding}) .then(function (str) { return searchFn(str); }).catch(function () { callback(""); }); } /** * one it finds the line which has any of error String * after parsing the Log * it notifies the bracket. * @param{Object} searchParams is object contains Information Error String * Encoding of Log File Update Diectory Path. */ function checkInstallerStatus(requester, searchParams) { var installErrorStr = searchParams.installErrorStr, bracketsErrorStr = searchParams.bracketsErrorStr, updateDirectory = searchParams.updateDir, encoding = searchParams.encoding || "utf8", statusObj = {installError: ": BA_UN"}, logFileAvailable = false, currentRequester = requester || ""; var notifyBrackets = function notifyBrackets(errorline) { statusObj.installError = errorline || ": BA_UN"; postMessageToBrackets(MessageIds.NOTIFY_INSTALLATION_STATUS, currentRequester, statusObj); }; var parseLog = function (files) { files.forEach(function (file) { var fileExt = path.extname(path.basename(file)); if (fileExt === ".logs") { var fileName = path.basename(file), fileFullPath = updateDirectory + '/' + file; if (fileName.search("installStatus.logs") !== -1) { logFileAvailable = true; parseInstallerLog(fileFullPath, bracketsErrorStr, "utf8", notifyBrackets); } else if (fileName.search("update.logs") !== -1) { logFileAvailable = true; parseInstallerLog(fileFullPath, installErrorStr, encoding, notifyBrackets); } } }); if (!logFileAvailable) { postMessageToBrackets(MessageIds.NOTIFY_INSTALLATION_STATUS, currentRequester, statusObj); } }; fs.readdir(updateDirectory) .then(function (files) { return parseLog(files); }).catch(function () { postMessageToBrackets(MessageIds.NOTIFY_INSTALLATION_STATUS, currentRequester, statusObj); }); } /** * Downloads the installer for latest Brackets release * @param {boolean} sendInfo - true if download status info needs to be * sent back to Brackets, false otherwise * @param {object} [updateParams=_updateParams] - json containing update parameters */ function downloadInstaller(requester, isInitialAttempt, updateParams) { updateParams = updateParams || _updateParams; var currentRequester = requester || ""; try { var ext = path.extname(updateParams.installerName); var localInstallerPath = path.resolve(updateDir, Date.now().toString() + ext), localInstallerFile = fs.createWriteStream(localInstallerPath), requestCompleted = true, readTimeOut = 180000; progress(request(updateParams.downloadURL, {timeout: readTimeOut}), {}) .on('progress', function (state) { var target = "retry-download"; if (isInitialAttempt) { target = "initial-download"; } var info = Math.floor(parseFloat(state.percent) * 100).toString() + '%'; var status = { target: target, spans: [{ id: "percent", val: info }] }; postMessageToBrackets(MessageIds.SHOW_STATUS_INFO, currentRequester, status); }) .on('error', function (err) { console.log("AutoUpdate : Download failed. Error occurred : " + err.toString()); requestCompleted = false; localInstallerFile.end(); var error = err.code === 'ESOCKETTIMEDOUT' || err.code === 'ENOTFOUND' ? nodeErrorMessages.NETWORK_SLOW_OR_DISCONNECTED : nodeErrorMessages.DOWNLOAD_ERROR; postMessageToBrackets(MessageIds.NOTIFY_DOWNLOAD_FAILURE, currentRequester, error); }) .pipe(localInstallerFile) .on('close', function () { if (requestCompleted) { try { fs.renameSync(localInstallerPath, installerPath); postMessageToBrackets(MessageIds.NOTIFY_DOWNLOAD_SUCCESS, currentRequester); } catch (e) { console.log("AutoUpdate : Download failed. Exception occurred : " + e.toString()); postMessageToBrackets(MessageIds.NOTIFY_DOWNLOAD_FAILURE, currentRequester, nodeErrorMessages.DOWNLOAD_ERROR); } } }); } catch (e) { console.log("AutoUpdate : Download failed. Exception occurred : " + e.toString()); postMessageToBrackets(MessageIds.NOTIFY_DOWNLOAD_FAILURE, currentRequester, nodeErrorMessages.DOWNLOAD_ERROR); } } /** * Performs clean up for the contents in Update Directory in AppData * @param {Array} filesToCache - array of file types to cache * @param {boolean} notifyBack - true if Brackets needs to be * notified post cleanup, false otherwise */ function performCleanup(requester, filesToCache, notifyBack) { var currentRequester = requester || ""; function filterFilesAndNotify(files, filesToCacheArr, notifyBackToBrackets) { files.forEach(function (file) { var fileExt = path.extname(path.basename(file)); if (filesToCacheArr.indexOf(fileExt) < 0) { var fileFullPath = updateDir + '/' + file; try { fs.removeSync(fileFullPath); } catch (e) { console.log("AutoUpdate : Exception occured in removing ", fileFullPath, e); } } }); if (notifyBackToBrackets) { postMessageToBrackets(MessageIds.NOTIFY_SAFE_TO_DOWNLOAD, currentRequester); } } fs.stat(updateDir) .then(function (stats) { if (stats) { if (filesToCache) { fs.readdir(updateDir) .then(function (files) { filterFilesAndNotify(files, filesToCache, notifyBack); }) .catch(function (err) { console.log("AutoUpdate : Error in Reading Update Dir for Cleanup : " + err.toString()); postMessageToBrackets(MessageIds.SHOW_ERROR_MESSAGE, currentRequester, nodeErrorMessages.UPDATEDIR_READ_FAILED); }); } else { fs.remove(updateDir) .then(function () { console.log('AutoUpdate : Update Directory in AppData Cleaned: Complete'); }) .catch(function (err) { console.log("AutoUpdate : Error in Cleaning Update Dir : " + err.toString()); postMessageToBrackets(MessageIds.SHOW_ERROR_MESSAGE, currentRequester, nodeErrorMessages.UPDATEDIR_CLEAN_FAILED); }); } } }) .catch(function (err) { console.log("AutoUpdate : Error in Reading Update Dir stats for Cleanup : " + err.toString()); postMessageToBrackets(MessageIds.SHOW_ERROR_MESSAGE, currentRequester, nodeErrorMessages.UPDATEDIR_CLEAN_FAILED); }); } /** * Initializes the node with update parameters * @param {object} updateParams - json containing update parameters */ function initializeState(requester, updateParams) { var currentRequester = requester || ""; _updateParams = updateParams; installerPath = path.resolve(updateDir, updateParams.installerName); postMessageToBrackets(MessageIds.NOTIFY_INITIALIZATION_COMPLETE, currentRequester); } function removeFromRequesters(requester) { if (requesters.hasOwnProperty(requester.toString())) { delete requesters[requester]; } } /** * Generates a map for node side functions */ function registerNodeFunctions() { functionMap["node.downloadInstaller"] = downloadInstaller; functionMap["node.performCleanup"] = performCleanup; functionMap["node.validateInstaller"] = validateChecksum; functionMap["node.initializeState"] = initializeState; functionMap["node.checkInstallerStatus"] = checkInstallerStatus; functionMap["node.removeFromRequesters"] = removeFromRequesters; } /** * Initializes node for the auto update, registers messages and node side funtions * @param {object} initObj - json containing init information { * messageIds : Messages for brackets and node communication * updateDir : update directory in Appdata * requester : ID of the current requester domain} */ function initNode(initObj) { var resetUpdateProgres = false; if (!isNodeDomainInitialized) { MessageIds = initObj.messageIds; updateDir = path.resolve(initObj.updateDir); logFilePath = path.resolve(updateDir, logFile); installStatusFilePath = path.resolve(updateDir, installStatusFile); registerNodeFunctions(); isNodeDomainInitialized = true; resetUpdateProgres = true; } postMessageToBrackets(MessageIds.NODE_DOMAIN_INITIALIZED, initObj.requester.toString(), resetUpdateProgres); requesters[initObj.requester.toString()] = true; postMessageToBrackets(MessageIds.REGISTER_BRACKETS_FUNCTIONS, initObj.requester.toString()); } /** * Receives messages from brackets * @param {object} msgObj - json containing - { * fn - function to execute on node side * args - arguments to the above function } */ function receiveMessageFromBrackets(msgObj) { var argList = msgObj.args; argList.unshift(msgObj.requester || ""); functionMap[msgObj.fn].apply(null, argList); } /** * Initialize the domain with commands and events related to AutoUpdate * @param {DomainManager} domainManager - The DomainManager for AutoUpdateDomain */ function init(domainManager) { if (!domainManager.hasDomain("AutoUpdate")) { domainManager.registerDomain("AutoUpdate", { major: 0, minor: 1 }); } _domainManager = domainManager; domainManager.registerCommand( "AutoUpdate", "initNode", initNode, true, "Initializes node for the auto update", [ { name: "initObj", type: "object", description: "json object containing init information" } ], [] ); domainManager.registerCommand( "AutoUpdate", "data", receiveMessageFromBrackets, true, "Receives messages from brackets", [ { name: "msgObj", type: "object", description: "json object containing message info" } ], [] ); domainManager.registerEvent( "AutoUpdate", "data", [ { name: "msgObj", type: "object", description: "json object containing message info to pass to brackets" } ] ); } exports.init = init; }()); ================================================ FILE: src/extensions/default/AutoUpdate/node/package.json ================================================ { "name": "brackets-auto-update", "dependencies": { "request": "^2.83.0", "request-progress": "^3.0.0", "fs-extra": "^5.0.0" } } ================================================ FILE: src/extensions/default/AutoUpdate/styles/styles.css ================================================ /* * Copyright (c) 2018 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /*Status bar*/ #update-status { position: relative; float: right; padding: 0px 20px; height: 25px; min-width: 9%; width: auto; text-align: center; background: #fff; } .dark #update-status { background: #1c1c1e; } #update-status p { position: relative; display: none; white-space: nowrap; font-family: 'SourceSansPro'; } /*Update Bar*/ #update-bar { display: block; background-color: #105F9C; box-shadow: 0px 3px 6px rgba(0, 0, 0, 0.53); height: 38px; width: 100%; position: absolute; z-index: 15; left: 0px; bottom: 25px; outline: none; overflow: hidden; } #update-bar #icon-container { width: auto; height: auto; padding: 11px; float: left; } #update-bar #icon-container #update-icon { background: url("../images/info.svg") no-repeat 0 0; width: 16px; height: 16px; display: block; } #update-bar #content-container { padding: 10px 7px; float: left; max-width: 78%; } #update-bar #content-container #update-content { margin: 0px !important; /*Check if this important is necessary*/ line-height: 18px; font-size: 14px; font-family: 'SourceSansPro'; color: #FFFFFF; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } #update-bar #content-container #update-content #heading{ font-weight: bold; } /*For focussed link of brackets.io*/ #update-bar #content-container #update-content #description a:focus{ box-shadow: none; } #update-bar #content-container #update-content #description a{ text-decoration: underline; color: #FFFFFF; } #update-bar #button-container { display: block; float: right; right: 40px; position: fixed; background-color: #105F9C; min-width: 180px; } #update-bar #close-icon-container { height: auto; padding: 9px; position: fixed; float: right; text-align: center; width: auto; min-width: 66px; right: 30px; background-color: #105F9C; } #update-bar #close-icon-container #close-icon { display: block; color: white; font-size: 18px; line-height: 18px; text-decoration: none; width: 18px; height: 18px; background-color: transparent; border: none; padding: 0px; /*This is needed to center the icon*/ float: right; } #update-bar #close-icon-container #close-icon:hover { background-color: rgba(255, 255, 255 ,0.16); border-radius: 50%; } #update-bar #close-icon-container #close-icon:focus { background-color: rgba(255, 255, 255 ,0.16); border-radius: 50%; border: 1px solid #C3E3FF; outline: 0; } #update-bar #close-icon-container #close-icon:focus:active { background-color: rgba(255, 255, 255 ,0.32); border: none; } .update-btn { width: auto; height: 28px; position: relative; float: right; padding: 4px 15px; border: 1px solid #EAEAEA; border-radius: 3px; font-size: 14px; text-align: center; font-family: 'SourceSansPro'; color: #E6E6E6; margin-top: 5px; margin-right: 10px; background-color: transparent; } .update-btn:hover { border-color: #C9C9C9; background-color: #EAEAEA; color: #202020; } .update-btn:focus:active { border: 1px solid #B5B5B5; background-color: #CCCCCC; color: #202020; padding: 4px 15px; box-shadow: none; } .update-btn:focus { border: 2px solid #C3E3FF; background-color: #EAEAEA; color: #202020; box-shadow: 0px 3px 6px rgba(148, 206, 255, 0.23); padding: 3px 14px; } /*Warning Message in Update Bar*/ #update-bar.warning, #update-bar.warning #close-icon-container { background-color: #DA7A12; } .dark #update-bar.warning, .dark #update-bar.warning #close-icon-container { background-color: #E6851A; } #update-bar.warning #icon-container #update-icon, #update-bar.error #icon-container #update-icon { background: url("../images/alert.svg") no-repeat 0 0; } /*Error message in Update Bar*/ #update-bar.error, #update-bar.error #close-icon-container { background-color: #D7373F; } .dark #update-bar.error, .dark #update-bar.error #close-icon-container{ background-color: #E4484F; } /*Success message in Update Bar*/ #update-bar.success, #update-bar.success #close-icon-container { background-color: #278E6B; } .dark #update-bar.success, .dark #update-bar.success #close-icon-container { background-color: #2E9D77; } #update-bar.success #icon-container #update-icon{ background: url("../images/checkmarkcircle.svg") no-repeat 0 0; } /*Overrides*/ #status-indicators { position: relative; float: right; } ================================================ FILE: src/extensions/default/CSSAtRuleCodeHints/AtRulesDef.json ================================================ { "@charset": "Defines the character set used by the style sheet.", "@counter-style": "Defines specific counter styles that are not part of the predefined set of styles.", "@font-face": "Describes the aspect of an external font to be downloaded.", "@font-feature-values": "Defines common names in font-variant-alternates for feature activated differently in OpenType.", "@import": "Tells the CSS engine to include an external style sheet.", "@keyframes": "Describes the aspect of intermediate steps in a CSS animation sequence.", "@media": "A conditional group rule which will apply its content if the device meets the criteria of the condition defined using a media query.", "@namespace": "Tells the CSS engine that all its content must be considered prefixed with an XML namespace.", "@page": "Describes the aspect of layout changes which will be applied when printing the document.", "@supports": "A conditional group rule which will apply its content if the browser meets the criteria of the given condition." } ================================================ FILE: src/extensions/default/CSSAtRuleCodeHints/main.js ================================================ /* * Copyright (c) 2017 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ define(function (require, exports, module) { "use strict"; // Load dependent modules var AppInit = brackets.getModule("utils/AppInit"), CodeHintManager = brackets.getModule("editor/CodeHintManager"), AtRulesText = require("text!AtRulesDef.json"), AtRules = JSON.parse(AtRulesText); /** * @constructor */ function AtRuleHints() { } // As we are only going to provide @rules name hints // we should claim that we don't have hints for anything else AtRuleHints.prototype.hasHints = function (editor, implicitChar) { var pos = editor.getCursorPos(), token = editor._codeMirror.getTokenAt(pos), cmState; this.editor = editor; if (token.state.base && token.state.base.localState) { cmState = token.state.base.localState; } else { cmState = token.state.localState || token.state; } // Check if we are at '@' rule 'def' context if ((token.type === "def" && cmState.context.type === "at") || (token.type === "variable-2" && (cmState.context.type === "top" || cmState.context.type === "block"))) { this.filter = token.string; return true; } else { this.filter = null; return false; } }; AtRuleHints.prototype.getHints = function (implicitChar) { var pos = this.editor.getCursorPos(), token = this.editor._codeMirror.getTokenAt(pos); this.filter = token.string; this.token = token; if (!this.filter) { return null; } // Filter the property list based on the token string var result = Object.keys(AtRules).filter(function (key) { if (key.indexOf(token.string) === 0) { return key; } }).sort(); return { hints: result, match: this.filter, selectInitial: true, defaultDescriptionWidth: true, handleWideResults: false }; }; /** * Inserts a given @ hint into the current editor context. * * @param {string} completion * The hint to be inserted into the editor context. * * @return {boolean} * Indicates whether the manager should follow hint insertion with an * additional explicit hint request. */ AtRuleHints.prototype.insertHint = function (completion) { var cursor = this.editor.getCursorPos(); this.editor.document.replaceRange(completion, {line: cursor.line, ch: this.token.start}, {line: cursor.line, ch: this.token.end}); return false; }; AppInit.appReady(function () { // Register code hint providers var restrictedBlockHints = new AtRuleHints(); CodeHintManager.registerHintProvider(restrictedBlockHints, ["css", "less", "scss"], 0); // For unit testing exports.restrictedBlockHints = restrictedBlockHints; }); }); ================================================ FILE: src/extensions/default/CSSAtRuleCodeHints/unittests.js ================================================ /* * Copyright (c) 2017 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /*global describe, it, xit, expect, beforeEach, afterEach */ define(function (require, exports, module) { "use strict"; var SpecRunnerUtils = brackets.getModule("spec/SpecRunnerUtils"), CSSAtRuleCodeHints = require("main"); describe("CSS '@' rules Code Hinting", function () { var defaultContent = "@ { \n" + "} \n" + " \n" + "@m "; var testDocument, testEditor; /* * Create a mockup editor with the given content and language id. * * @param {string} content - content for test window * @param {string} languageId */ function setupTest(content, languageId) { var mock = SpecRunnerUtils.createMockEditor(content, languageId); testDocument = mock.doc; testEditor = mock.editor; } function tearDownTest() { SpecRunnerUtils.destroyMockEditor(testDocument); testEditor = null; testDocument = null; } // Ask provider for hints at current cursor position; expect it to return some function expectHints(provider, implicitChar, returnWholeObj) { expect(provider.hasHints(testEditor, implicitChar)).toBe(true); var hintsObj = provider.getHints(); expect(hintsObj).toBeTruthy(); // return just the array of hints if returnWholeObj is falsy return returnWholeObj ? hintsObj : hintsObj.hints; } // Ask provider for hints at current cursor position; expect it NOT to return any function expectNoHints(provider, implicitChar) { expect(provider.hasHints(testEditor, implicitChar)).toBe(false); } // compares lists to ensure they are the same function verifyListsAreIdentical(hintList, values) { var i; expect(hintList.length).toBe(values.length); for (i = 0; i < values.length; i++) { expect(hintList[i]).toBe(values[i]); } } function selectHint(provider, expectedHint, implicitChar) { var hintList = expectHints(provider, implicitChar); expect(hintList.indexOf(expectedHint)).not.toBe(-1); return provider.insertHint(expectedHint); } // Helper function for testing cursor position function fixPos(pos) { if (!("sticky" in pos)) { pos.sticky = null; } return pos; } function expectCursorAt(pos) { var selection = testEditor.getSelection(); expect(fixPos(selection.start)).toEqual(fixPos(selection.end)); expect(fixPos(selection.start)).toEqual(fixPos(pos)); } function verifyFirstEntry(hintList, expectedFirstHint) { expect(hintList[0]).toBe(expectedFirstHint); } // Helper function to // a) ensure the hintList and the list with the available values have the same size // b) ensure that all possible values are mentioned in the hintList function verifyAllValues(hintList, values) { expect(hintList.length).toBe(values.length); expect(hintList.sort().toString()).toBe(values.sort().toString()); } var modesToTest = ['css', 'scss', 'less'], modeCounter; var selectMode = function () { return modesToTest[modeCounter]; }; describe("'@' rules in styles mode (selection of correct restricted block based on input)", function () { beforeEach(function () { // create Editor instance (containing a CodeMirror instance) var mock = SpecRunnerUtils.createMockEditor(defaultContent, selectMode()); testEditor = mock.editor; testDocument = mock.doc; }); afterEach(function () { SpecRunnerUtils.destroyMockEditor(testDocument); testEditor = null; testDocument = null; }); var testAllHints = function () { testEditor.setCursorPos({ line: 0, ch: 1 }); // after @ var hintList = expectHints(CSSAtRuleCodeHints.restrictedBlockHints); verifyFirstEntry(hintList, "@charset"); // filtered on "empty string" verifyListsAreIdentical(hintList, ["@charset", "@counter-style", "@font-face", "@font-feature-values", "@import", "@keyframes", "@media", "@namespace", "@page", "@supports"]); }, testFilteredHints = function () { testEditor.setCursorPos({ line: 3, ch: 2 }); // after @m var hintList = expectHints(CSSAtRuleCodeHints.restrictedBlockHints); verifyFirstEntry(hintList, "@media"); // filtered on "@m" verifyListsAreIdentical(hintList, ["@media"]); }, testNoHintsOnSpace = function () { testEditor.setCursorPos({ line: 3, ch: 3 }); // after { expect(CSSAtRuleCodeHints.restrictedBlockHints.hasHints(testEditor, '')).toBe(false); }, testNoHints = function () { testEditor.setCursorPos({ line: 0, ch: 0 }); // after { expect(CSSAtRuleCodeHints.restrictedBlockHints.hasHints(testEditor, 'c')).toBe(false); }; for (modeCounter in modesToTest) { it("should list all rule hints right after @", testAllHints); it("should list filtered rule hints right after @m", testFilteredHints); it("should not list rule hints on space", testNoHintsOnSpace); it("should not list rule hints if the cursor is before @", testNoHints); } }); describe("'@' rules in LESS mode (selection of correct restricted block based on input)", function () { defaultContent = "@ { \n" + "} \n" + " \n" + "@m \n" + "@green: green;\n" + ".div { \n" + "color: @" + "} \n"; beforeEach(function () { // create Editor instance (containing a CodeMirror instance) var mock = SpecRunnerUtils.createMockEditor(defaultContent, "less"); testEditor = mock.editor; testDocument = mock.doc; }); afterEach(function () { SpecRunnerUtils.destroyMockEditor(testDocument); testEditor = null; testDocument = null; }); it("should not list rule hints in less variable evaluation scope", function () { testEditor.setCursorPos({ line: 3, ch: 3 }); // after { expect(CSSAtRuleCodeHints.restrictedBlockHints.hasHints(testEditor, '')).toBe(false); }); }); describe("'@' rule hint insertion", function () { beforeEach(function () { // create Editor instance (containing a CodeMirror instance) var mock = SpecRunnerUtils.createMockEditor(defaultContent, "css"); testEditor = mock.editor; testDocument = mock.doc; }); afterEach(function () { SpecRunnerUtils.destroyMockEditor(testDocument); testEditor = null; testDocument = null; }); it("should insert @rule selected", function () { testEditor.setCursorPos({ line: 0, ch: 1 }); // cursor after '@' selectHint(CSSAtRuleCodeHints.restrictedBlockHints, "@charset"); expect(testDocument.getLine(0)).toBe("@charset { "); expectCursorAt({ line: 0, ch: 8 }); }); it("should insert filtered selection by replacing the existing rule", function () { testEditor.setCursorPos({ line: 3, ch: 2 }); // cursor after '@m' selectHint(CSSAtRuleCodeHints.restrictedBlockHints, "@media"); expect(testDocument.getLine(3)).toBe("@media "); expectCursorAt({ line: 3, ch: 6 }); }); }); }); }); ================================================ FILE: src/extensions/default/CSSCodeHints/CSSProperties.json ================================================ { "align-content": {"values": ["center", "flex-end", "flex-start", "space-around", "space-between", "stretch"]}, "align-items": {"values": ["baseline", "center", "flex-end", "flex-start", "stretch"]}, "align-self": {"values": ["auto", "baseline", "center", "flex-end", "flex-start", "stretch"]}, "all": {"values": []}, "animation": {"values": []}, "animation-delay": {"values": []}, "animation-direction": {"values": ["alternate", "alternate-reverse", "normal", "reverse"]}, "animation-duration": {"values": []}, "animation-fill-mode": {"values": ["backwards", "both", "forwards", "none"]}, "animation-iteration-count": {"values": ["infinite"]}, "animation-name": {"values": ["none"]}, "animation-play-state": {"values": ["paused", "running"]}, "animation-timing-function": {"values": ["cubic-bezier()", "ease", "ease-in", "ease-in-out", "ease-out", "linear", "step-end", "step-start", "steps()"]}, "backface-visibility": {"values": ["hidden", "visible"]}, "background": {"values": []}, "background-attachment": {"values": ["fixed", "local", "scroll", "inherit"]}, "background-blend-mode": {"values": ["color", "color-burn", "color-dodge", "darken", "difference", "exclusion", "hard-light", "hue", "lighten", "luminosity", "multiply", "normal", "overlay", "saturation", "screen", "soft-light"]}, "background-clip": {"values": ["border-box", "content-box", "padding-box", "inherit"]}, "background-color": {"values": ["inherit"], "type": "color"}, "background-image": {"values": ["image()", "linear-gradient()", "radial-gradient()", "repeating-linear-gradient()", "repeating-radial-gradient()", "url()"]}, "background-origin": {"values": ["border-box", "content-box", "padding-box", "inherit"]}, "background-position": {"values": ["left", "center", "right", "bottom", "top"]}, "background-repeat": {"values": ["no-repeat", "repeat", "repeat-x", "repeat-y", "round", "space"]}, "background-size": {"values": ["auto", "contain", "cover"]}, "border": {"values": []}, "border-collapse": {"values": ["collapse", "separate", "inherit"]}, "border-color": {"values": ["inherit"], "type": "color"}, "border-spacing": {"values": ["inherit"]}, "border-style": {"values": ["dashed", "dotted", "double", "groove", "hidden", "inset", "none", "outset", "ridge", "solid", "inherit"]}, "border-bottom": {"values": []}, "border-bottom-color": {"values": ["inherit"], "type": "color"}, "border-bottom-left-radius": {"values": []}, "border-bottom-right-radius": {"values": []}, "border-bottom-style": {"values": ["dashed", "dotted", "double", "groove", "hidden", "inset", "none", "outset", "ridge", "solid", "inherit"]}, "border-bottom-width": {"values": ["medium", "thin", "thick", "inherit"]}, "border-image": {"values": [ "url()" ]}, "border-image-outset": {"values": []}, "border-image-slice": {"values": []}, "border-image-source": {"values": []}, "border-image-repeat": {"values": ["repeat", "round", "space", "stretch"]}, "border-image-width": {"values": ["auto"]}, "border-left": {"values": []}, "border-left-color": {"values": ["inherit"], "type": "color"}, "border-left-style": {"values": ["dashed", "dotted", "double", "groove", "hidden", "inset", "none", "outset", "ridge", "solid", "inherit"]}, "border-left-width": {"values": ["medium", "thin", "thick", "inherit"]}, "border-radius": {"values": []}, "border-right": {"values": []}, "border-right-color": {"values": ["inherit"], "type": "color"}, "border-right-style": {"values": ["dashed", "dotted", "double", "groove", "hidden", "inset", "none", "outset", "ridge", "solid", "inherit"]}, "border-right-width": {"values": ["medium", "thin", "thick", "inherit"]}, "border-top": {"values": []}, "border-top-color": {"values": ["inherit"], "type": "color"}, "border-top-left-radius": {"values": []}, "border-top-right-radius": {"values": []}, "border-top-style": {"values": ["dashed", "dotted", "double", "groove", "hidden", "inset", "none", "outset", "ridge", "solid", "inherit"]}, "border-top-width": {"values": ["medium", "thin", "thick", "inherit"]}, "border-width": {"values": ["medium", "thin", "thick", "inherit"]}, "box-decoration-break": {"values": ["clone", "slice"]}, "box-shadow": {"values": []}, "box-sizing": {"values": ["border-box", "content-box", "inherit"]}, "bottom": {"values": ["auto", "inherit"]}, "break-after": {"values": ["always", "auto", "avoid", "avoid-column", "avoid-page", "avoid-region", "column", "left", "page", "region", "right"]}, "break-before": {"values": ["always", "auto", "avoid", "avoid-column", "avoid-page", "avoid-region", "column", "left", "page", "region", "right"]}, "break-inside": {"values": ["auto", "avoid", "avoid-column", "avoid-page", "avoid-region"]}, "caption-side": {"values": ["bottom", "top", "inherit"]}, "caret-color": {"values": ["auto"], "type": "color"}, "clear": {"values": ["both", "left", "none", "right", "inherit"]}, "clip": {"values": ["auto", "inherit"]}, "color": {"values": ["inherit"], "type": "color"}, "columns": {"values": []}, "column-count": {"values": []}, "column-fill": {"values": ["auto", "balance"]}, "column-gap": {"values": ["normal"]}, "column-rule": {"values": []}, "column-rule-color": {"values": [], "type": "color"}, "column-rule-style": {"values": ["dashed", "dotted", "double", "groove", "hidden", "inset", "none", "outset", "ridge", "solid", "inherit"]}, "column-rule-width": {"values": ["medium", "thin", "thick", "inherit"]}, "column-span": {"values": ["all", "none"]}, "column-width": {"values": ["auto", "inherit"]}, "content": {"values": ["attr()", "close-quote", "no-close-quote", "no-open-quote", "normal", "none", "open-quote", "inherit"]}, "counter-increment": {"values": ["none", "inherit"]}, "counter-reset": {"values": ["none", "inherit"]}, "cursor": {"values": ["alias", "all-scroll", "auto", "cell", "col-resize", "context-menu", "copy", "crosshair", "default", "e-resize", "ew-resize", "grab", "grabbing", "help", "inherit", "move", "n-resize", "ne-resize", "nesw-resize", "no-drop", "none", "not-allowed", "ns-resize", "nw-resize", "nwse-resize", "pointer", "progress", "row-resize", "s-resize", "se-resize", "sw-resize", "text", "vertical-text", "w-resize", "wait", "zoom-in", "zoom-out"]}, "direction": {"values": ["ltr", "rtl", "inherit"]}, "display": {"values": ["block", "contents", "flex", "flow-root", "grid", "inline", "inline-block", "inline-flex", "inline-grid", "inline-table", "list-item", "none", "run-in", "subgrid", "table", "table-caption", "table-cell", "table-column", "table-column-group", "table-footer-group", "table-header-group", "table-row", "table-row-group", "inherit"]}, "empty-cells": {"values": ["hide", "show", "inherit"]}, "filter": {"values": ["blur()", "brightness()", "contrast()", "custom()", "drop-shadow()", "grayscale()", "hue-rotate()", "invert()", "none", "opacity()", "sepia()", "saturate()", "url()"]}, "flex": {"values": ["auto", "initial", "none"]}, "flex-basis": {"values": ["auto"]}, "flex-direction": {"values": ["column", "column-reverse", "row", "row-reverse"]}, "flex-flow": {"values": ["column", "column-reverse", "nowrap", "row", "row-reverse", "wrap", "wrap-reverse"]}, "flex-grow": {"values": []}, "flex-shrink": {"values": []}, "flex-wrap": {"values": ["nowrap", "wrap", "wrap-reverse"]}, "float": {"values": ["left", "right", "none", "inherit"]}, "flow-into": {"values": ["none"], "type": "named-flow"}, "flow-from": {"values": ["none", "inherit"], "type": "named-flow"}, "font": {"values": []}, "font-display": {"values": ["auto", "block", "swap", "fallback", "optional"]}, "font-family": {"values": ["cursive", "fantasy", "inherit", "monospace", "sans-serif", "serif"]}, "font-feature-settings": {"values": ["normal"]}, "font-kerning": {"values": ["auto", "none", "normal"]}, "font-language-override": {"values": ["normal"]}, "font-size": {"values": []}, "font-size-adjust": {"values": ["auto", "none"]}, "font-stretch": {"values": ["condensed", "expanded", "extra-condensed", "extra-expanded", "normal", "semi-condensed", "semi-expanded", "ultra-condensed", "ultra-expanded"]}, "font-style": {"values": ["italic", "normal", "oblique"]}, "font-synthesis": {"values": ["none", "style", "weight"]}, "font-variant": {"values": ["normal", "small-caps", "inherit"]}, "font-variant-alternates": {"values": ["normal"]}, "font-variant-caps": {"values": ["normal", "small-caps", "all-small-caps", "petite-caps", "all-petite-caps", "unicase", "titling-caps"]}, "font-variant-east-asian": {"values": ["normal"]}, "font-variant-ligatures": {"values": ["normal", "none"]}, "font-variant-numeric": {"values": ["normal"]}, "font-variant-position": {"values": ["normal", "sub", "super"]}, "font-weight": {"values": ["bold", "bolder", "lighter", "normal", "100", "200", "300", "400", "500", "600", "700", "800", "900", "inherit"]}, "grid": {"values": []}, "grid-area": {"values": []}, "grid-auto-columns": {"values": []}, "grid-auto-flow": {"values": ["row", "column", "dense"]}, "grid-auto-rows": {"values": []}, "grid-column": {"values": ["auto"]}, "grid-column-end": {"values": []}, "grid-column-gap": {"values": []}, "grid-column-start": {"values": []}, "grid-gap": {"values": []}, "grid-row": {"values": ["auto"]}, "grid-row-end": {"values": []}, "grid-row-start": {"values": []}, "grid-row-gap": {"values": []}, "grid-template": {"values": ["none"]}, "grid-template-areas": {"values": []}, "grid-template-columns": {"values": ["auto"]}, "grid-template-rows": {"values": ["auto"]}, "hanging-punctuation": {"values": ["allow-end", "first", "force-end", "last", "none"]}, "height": {"values": ["auto", "inherit"]}, "hyphens": {"values": ["auto", "manual", "none"]}, "image-orientation": {"values": []}, "image-resolution": {"values": ["from-image", "snap"]}, "isolation": {"values": ["auto", "isolate"]}, "justify-content": {"values": ["center", "flex-end", "flex-start", "space-around", "space-between"]}, "justify-items": {"values": ["auto", "normal", "stretch", "center", "start", "end", "flex-start", "flex-end", "self-start", "self-end", "left", "right", "baseline", "first", "last", "safe", "unsafe", "legacy", "inherit", "initial"]}, "justify-self": {"values": ["auto", "normal", "stretch", "center", "start", "end", "flex-start", "flex-end", "self-start", "self-end", "left", "right", "baseline", "first", "last", "safe", "unsafe", "inherit", "initial"]}, "left": {"values": ["auto", "inherit"]}, "letter-spacing": {"values": ["normal", "inherit"]}, "line-height": {"values": ["normal", "inherit"]}, "list-style": {"values": ["none", "inherit", "initial", "unset", "url()", "armenian", "circle", "decimal", "decimal-leading-zero", "disc", "georgian", "inside", "lower-alpha", "lower-greek", "lower-latin", "lower-roman", "outside", "square", "upper-alpha", "upper-latin", "upper-roman"]}, "list-style-image": {"values": ["none", "url()", "inherit"]}, "list-style-position": {"values": ["inside", "outside", "inherit"]}, "list-style-type": {"values": ["armenian", "circle", "decimal", "decimal-leading-zero", "disc", "georgian", "lower-alpha", "lower-greek", "lower-latin", "lower-roman", "none", "square", "upper-alpha", "upper-latin", "upper-roman", "inherit"]}, "margin": {"values": ["auto", "inherit"]}, "margin-bottom": {"values": ["auto", "inherit"]}, "margin-left": {"values": ["auto", "inherit"]}, "margin-right": {"values": ["auto", "inherit"]}, "margin-top": {"values": ["auto", "inherit"]}, "max-height": {"values": ["none", "inherit"]}, "max-width": {"values": ["none", "inherit"]}, "min-height": {"values": ["inherit"]}, "min-width": {"values": ["inherit"]}, "mix-blend-mode": {"values": ["color", "color-burn", "color-dodge", "darken", "difference", "exclusion", "hard-light", "hue", "lighten", "luminosity", "multiply", "normal", "overlay", "saturation", "screen", "soft-light"]}, "object-fit": {"values": ["contain", "cover", "fill", "none", "scale-down"]}, "object-position": {"values": ["left", "center", "right", "bottom", "top"]}, "opacity": {"values": ["inherit"]}, "order": {"values": []}, "orphans": {"values": ["inherit"]}, "outline": {"values": ["inherit"]}, "outline-color": {"values": ["invert", "inherit"], "type": "color"}, "outline-offset": {"values": ["inherit"]}, "outline-style": {"values": ["dashed", "dotted", "double", "groove", "hidden", "inset", "none", "outset", "ridge", "solid", "inherit"]}, "outline-width": {"values": ["medium", "thin", "thick", "inherit"]}, "overflow": {"values": ["auto", "hidden", "scroll", "visible", "inherit"]}, "overflow-x": {"values": ["auto", "hidden", "scroll", "visible", "inherit"]}, "overflow-y": {"values": ["auto", "hidden", "scroll", "visible", "inherit"]}, "padding": {"values": ["inherit"]}, "padding-bottom": {"values": []}, "padding-left": {"values": []}, "padding-right": {"values": []}, "padding-top": {"values": []}, "page-break-after": {"values": ["always", "auto", "avoid", "left", "right", "inherit"]}, "page-break-before": {"values": ["always", "auto", "avoid", "left", "right", "inherit"]}, "page-break-inside": {"values": ["auto", "avoid", "inherit"]}, "perspective": {"values": ["none"]}, "perspective-origin": {"values": ["bottom", "center", "left", "right", "top"]}, "pointer-events": {"values": ["all", "auto", "fill", "inherit", "none", "painted", "stroke", "visible", "visibleFill", "visiblePainted", "visibleStroke"]}, "position": {"values": ["absolute", "fixed", "relative", "static", "sticky", "inherit"]}, "quotes": {"values": ["none", "inherit"]}, "region-break-after": {"values": ["always", "auto", "avoid", "avoid-column", "avoid-page", "avoid-region", "column", "left", "page", "region", "right"]}, "region-break-before": {"values": ["always", "auto", "avoid", "avoid-column", "avoid-page", "avoid-region", "column", "left", "page", "region", "right"]}, "region-break-inside": {"values": ["auto", "avoid", "avoid-column", "avoid-page", "avoid-region"]}, "region-fragment": {"values": ["auto", "break"]}, "resize": {"values": ["both", "horizontal", "none", "vertical", "inherit"]}, "right": {"values": ["auto", "inherit"]}, "scroll-behavior": {"values": ["auto", "smooth"]}, "scroll-snap-type": {"values": ["none", "x", "y", "block", "inline", "both", "mandatory", "proximity"]}, "src": {"values": [ "url()"]}, "shape-image-threshold": {"values": []}, "shape-inside": {"values": ["auto", "circle()", "ellipse()", "inherit", "outside-shape", "polygon()", "rectangle()"]}, "shape-margin": {"values": []}, "shape-outside": {"values": ["none", "inherit", "circle()", "ellipse()", "polygon()", "inset()", "margin-box", "border-box", "padding-box", "content-box", "url()", "image()", "linear-gradient()", "radial-gradient()", "repeating-linear-gradient()", "repeating-radial-gradient()"]}, "tab-size": {"values": []}, "table-layout": {"values": ["auto", "fixed", "inherit"]}, "text-align": {"values": ["start", "end", "center", "left", "justify", "right", "match-parent", "justify-all", "inherit"]}, "text-align-last": {"values": ["center", "left", "justify", "right", "inherit"]}, "text-decoration": {"values": ["line-through", "none", "overline", "underline", "inherit"]}, "text-decoration-color": {"values": [], "type": "color"}, "text-decoration-line": {"values": ["line-through", "none", "overline", "underline"]}, "text-decoration-skip": {"values": ["edges", "ink", "none", "objects", "spaces"]}, "text-decoration-style": {"values": ["dashed", "dotted", "double", "solid", "wavy"]}, "text-emphasis": {"values": []}, "text-emphasis-color": {"values": [], "type": "color"}, "text-emphasis-position": {"values": ["above", "below", "left", "right"]}, "text-emphasis-style": {"values": ["circle", "dot", "double-circle", "filled", "none", "open", "sesame", "triangle"]}, "text-indent": {"values": ["inherit"]}, "text-justify": {"values": ["auto", "none", "inter-word", "inter-character", "inherit"]}, "text-overflow": {"values": ["clip", "ellipsis", "inherit"]}, "text-shadow": {"values": []}, "text-rendering": {"values": ["auto", "geometricPrecision", "optimizeLegibility", "optimizeSpeed"]}, "text-transform": {"values": ["capitalize", "full-width", "lowercase", "none", "uppercase", "inherit"]}, "text-underline-position": {"values": ["alphabetic", "auto", "below", "left", "right"]}, "top": {"values": ["auto", "inherit"]}, "transform": {"values": ["matrix()", "matrix3d()", "none", "perspective()", "rotate()", "rotate3d()", "rotateX()", "rotateY()", "rotateZ()", "scale()", "scale3d()", "scaleX()", "scaleY()", "scaleZ()", "skewX()", "skewY()", "translate()", "translate3d()", "translateX()", "translateY()", "translateZ()"]}, "transform-origin": {"values": ["bottom", "center", "left", "right", "top"]}, "transform-style": {"values": ["flat", "preserve-3d"]}, "transition": {"values": []}, "transition-delay": {"values": []}, "transition-duration": {"values": []}, "transition-property": {"values": ["all", "none"]}, "transition-timing-function": {"values": ["cubic-bezier()", "ease", "ease-in", "ease-in-out", "ease-out", "linear", "step-end", "step-start", "steps()"]}, "unicode-bidi": {"values": ["bidi-override", "embed", "normal", "inherit"]}, "unicode-range": {"values": []}, "user-select": {"values": ["all", "auto", "contain", "none", "text"]}, "vertical-align": {"values": ["baseline", "bottom", "middle", "sub", "super", "text-bottom", "text-top", "top", "inherit"]}, "visibility": {"values": ["collapse", "hidden", "visible", "inherit"]}, "white-space": {"values": ["normal", "nowrap", "pre", "pre-line", "pre-wrap", "inherit"]}, "widows": {"values": ["inherit"]}, "width": {"values": ["auto", "inherit"]}, "will-change": {"values": ["auto", "contents", "opacity", "scroll-position", "transform", "inherit", "initial", "unset"]}, "word-break": {"values": ["normal", "break-all", "keep-all"]}, "word-spacing": {"values": ["normal", "inherit"]}, "word-wrap": {"values": ["break-word", "normal"]}, "z-index": {"values": ["auto", "inherit"]} } ================================================ FILE: src/extensions/default/CSSCodeHints/main.js ================================================ /* * Copyright (c) 2013 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /*jslint regexp: true */ define(function (require, exports, module) { "use strict"; var AppInit = brackets.getModule("utils/AppInit"), ExtensionUtils = brackets.getModule("utils/ExtensionUtils"), CodeHintManager = brackets.getModule("editor/CodeHintManager"), CSSUtils = brackets.getModule("language/CSSUtils"), HTMLUtils = brackets.getModule("language/HTMLUtils"), LanguageManager = brackets.getModule("language/LanguageManager"), PreferencesManager = brackets.getModule("preferences/PreferencesManager"), TokenUtils = brackets.getModule("utils/TokenUtils"), StringMatch = brackets.getModule("utils/StringMatch"), ColorUtils = brackets.getModule("utils/ColorUtils"), Strings = brackets.getModule("strings"), CSSProperties = require("text!CSSProperties.json"), properties = JSON.parse(CSSProperties); PreferencesManager.definePreference("codehint.CssPropHints", "boolean", true, { description: Strings.DESCRIPTION_CSS_PROP_HINTS }); // Context of the last request for hints: either CSSUtils.PROP_NAME, // CSSUtils.PROP_VALUE or null. var lastContext, stringMatcherOptions = { preferPrefixMatches: true }; /** * @constructor */ function CssPropHints() { this.primaryTriggerKeys = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-()"; this.secondaryTriggerKeys = ":"; this.exclusion = null; } /** * Get the CSS style text of the file open in the editor for this hinting session. * For a CSS file, this is just the text of the file. For an HTML file, * this will be only the text in the

      Example of flow from: some-named-flow;

      ...

      Lorem ipsum dolor ...

      Lorem ipsum dolor ...

      ================================================ FILE: src/extensions/default/CSSCodeHints/unittest-files/regions.css ================================================ /* basic tests */ article.content { flow-into: main; } section.layout > div { flow-from: main; } #jeff.content { flow-into: jeff; } #jeff.layout > div { flow-from: jeff; } /* exclude matches inside comments tests */ /* p.content { flow-into: carter; } p.layout > div { flow-from: carter; } */ /* exclude matches inside strings tests */ div { content: "/* p.content { flow-into: carter; } p.layout > div { flow-from: carter; } */"; } div { content: "html.content { flow-into: dexter; } html.layout > div { flow-from: dexter; }"; } /* div.content { content: "flow-into: martin;"; } div.layout > div { content: "flow-from: martin;"; } */ /* multi-line property tests */ #randy.content { flow-into: randy ; } #randy.layout > div { flow-from: randy; } /* test to exclude duplicates */ #yin.content { flow-from: ; flow-into: jeff; } #yin.layout > div { flow-from: jeff; } /* flow-from only tests */ #raymond.layout > div { flow-from: lim; } /* underscores and dashes */ #ingo.content { flow-from: edge-code_now_shipping; } /* invalid / ignored flows */ #test364.content { flow-from: default; flow-from: none; flow-from: inherit; flow-from: auto; flow-from: initial; flow-from: content; flow-from: element; } /* colors */ .colorful { color: ; background-color: ; border-left-color: deep; border-color: rent; height: ; color: transparent; } ================================================ FILE: src/extensions/default/CSSCodeHints/unittests.js ================================================ /* * Copyright (c) 2013 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /*global describe, it, xit, expect, beforeEach, afterEach */ define(function (require, exports, module) { "use strict"; var SpecRunnerUtils = brackets.getModule("spec/SpecRunnerUtils"), testContentCSS = require("text!unittest-files/regions.css"), testContentHTML = require("text!unittest-files/region-template.html"), CSSCodeHints = require("main"); describe("CSS Code Hinting", function () { var defaultContent = "@media screen { \n" + " body { \n" + " }\n" + "} \n" + ".selector { \n" + " \n" + " b\n" + " bord\n" + " border-\n" + " border-colo\n" + " border-color: red;\n" + // line: 10 " d\n" + " disp\n" + " display: \n" + " display: in\n" + " bordborder: \n" + " color\n" + "} \n"; var defaultHTMLContent = " \n" + " \n" + " \n" + " \n" + "
      \n" + // line 10 "
      \n" + " \n" + " \n"; var testDocument, testEditor; /* * Create a mockup editor with the given content and language id. * * @param {string} content - content for test window * @param {string} languageId */ function setupTest(content, languageId) { var mock = SpecRunnerUtils.createMockEditor(content, languageId); testDocument = mock.doc; testEditor = mock.editor; } function tearDownTest() { SpecRunnerUtils.destroyMockEditor(testDocument); testEditor = null; testDocument = null; } function extractHintList(hints) { return $.map(hints, function ($node) { return $node.text(); }); } // Ask provider for hints at current cursor position; expect it to return some function expectHints(provider, implicitChar, returnWholeObj) { expect(provider.hasHints(testEditor, implicitChar)).toBe(true); var hintsObj = provider.getHints(); expect(hintsObj).toBeTruthy(); // return just the array of hints if returnWholeObj is falsy return returnWholeObj ? hintsObj : extractHintList(hintsObj.hints); } // Ask provider for hints at current cursor position; expect it NOT to return any function expectNoHints(provider, implicitChar) { expect(provider.hasHints(testEditor, implicitChar)).toBe(false); } function verifyAttrHints(hintList, expectedFirstHint) { expect(hintList.indexOf("div")).toBe(-1); expect(hintList[0]).toBe(expectedFirstHint); } // compares lists to ensure they are the same function verifyListsAreIdentical(hintList, values) { var i; expect(hintList.length).toBe(values.length); for (i = 0; i < values.length; i++) { expect(hintList[i]).toBe(values[i]); } } function selectHint(provider, expectedHint, implicitChar) { var hintList = expectHints(provider, implicitChar); expect(hintList.indexOf(expectedHint)).not.toBe(-1); return provider.insertHint(expectedHint); } // Helper function for testing cursor position function fixPos(pos) { if (!("sticky" in pos)) { pos.sticky = null; } return pos; } function expectCursorAt(pos) { var selection = testEditor.getSelection(); expect(fixPos(selection.start)).toEqual(fixPos(selection.end)); expect(fixPos(selection.start)).toEqual(fixPos(pos)); } // Helper function to // a) ensure the hintList and the list with the available values have the same size // b) ensure that all possible values are mentioned in the hintList function verifyAllValues(hintList, values) { expect(hintList.length).toBe(values.length); expect(hintList.sort().toString()).toBe(values.sort().toString()); } describe("CSS properties in general (selection of correct property based on input)", function () { beforeEach(function () { // create Editor instance (containing a CodeMirror instance) var mock = SpecRunnerUtils.createMockEditor(defaultContent, "css"); testEditor = mock.editor; testDocument = mock.doc; }); afterEach(function () { SpecRunnerUtils.destroyMockEditor(testDocument); testEditor = null; testDocument = null; }); it("should list all prop-name hints right after curly bracket", function () { testEditor.setCursorPos({ line: 4, ch: 11 }); // after { var hintList = expectHints(CSSCodeHints.cssPropHintProvider); verifyAttrHints(hintList, "align-content"); // filtered on "empty string" }); it("should list all prop-name hints in new line", function () { testEditor.setCursorPos({ line: 5, ch: 1 }); var hintList = expectHints(CSSCodeHints.cssPropHintProvider); verifyAttrHints(hintList, "align-content"); // filtered on "empty string" }); it("should list all prop-name hints starting with 'b' in new line", function () { testEditor.setCursorPos({ line: 6, ch: 2 }); var hintList = expectHints(CSSCodeHints.cssPropHintProvider); verifyAttrHints(hintList, "backface-visibility"); // filtered on "b" }); it("should list all prop-name hints starting with 'bord' ", function () { // insert semicolon after previous rule to avoid incorrect tokenizing testDocument.replaceRange(";", { line: 6, ch: 2 }); testEditor.setCursorPos({ line: 7, ch: 5 }); var hintList = expectHints(CSSCodeHints.cssPropHintProvider); verifyAttrHints(hintList, "border"); // filtered on "bord" }); it("should list all prop-name hints starting with 'border-' ", function () { // insert semicolon after previous rule to avoid incorrect tokenizing testDocument.replaceRange(";", { line: 7, ch: 5 }); testEditor.setCursorPos({ line: 8, ch: 8 }); var hintList = expectHints(CSSCodeHints.cssPropHintProvider); verifyAttrHints(hintList, "border-bottom"); // filtered on "border-" }); it("should list only prop-name hint border-color", function () { // insert semicolon after previous rule to avoid incorrect tokenizing testDocument.replaceRange(";", { line: 8, ch: 8 }); testEditor.setCursorPos({ line: 9, ch: 12 }); var hintList = expectHints(CSSCodeHints.cssPropHintProvider); verifyAttrHints(hintList, "border-color"); // filtered on "border-color" verifyListsAreIdentical(hintList, ["border-color", "border-left-color", "border-top-color", "border-bottom-color", "border-right-color"]); }); it("should list prop-name hints at end of property-value finished by ;", function () { testEditor.setCursorPos({ line: 10, ch: 19 }); // after ; var hintList = expectHints(CSSCodeHints.cssPropHintProvider); verifyAttrHints(hintList, "align-content"); // filtered on "empty string" }); it("should NOT list prop-name hints right before curly bracket", function () { testEditor.setCursorPos({ line: 4, ch: 10 }); // inside .selector, before { expectNoHints(CSSCodeHints.cssPropHintProvider); }); it("should NOT list prop-name hints after declaration of mediatype", function () { testEditor.setCursorPos({ line: 0, ch: 15 }); // after { expectNoHints(CSSCodeHints.cssPropHintProvider); }); it("should NOT list prop-name hints if previous property is not closed properly", function () { testEditor.setCursorPos({ line: 16, ch: 6 }); // cursor directly after color expectNoHints(CSSCodeHints.cssPropHintProvider); }); it("should NOT list prop-name hints in media type declaration", function () { testEditor.setCursorPos({ line: 0, ch: 1 }); expect(CSSCodeHints.cssPropHintProvider.hasHints(testEditor, 'm')).toBe(false); }); }); describe("CSS property hint insertion", function () { beforeEach(function () { // create Editor instance (containing a CodeMirror instance) var mock = SpecRunnerUtils.createMockEditor(defaultContent, "css"); testEditor = mock.editor; testDocument = mock.doc; }); afterEach(function () { SpecRunnerUtils.destroyMockEditor(testDocument); testEditor = null; testDocument = null; }); it("should insert colon prop-name selected", function () { // insert semicolon after previous rule to avoid incorrect tokenizing testDocument.replaceRange(";", { line: 6, ch: 2 }); testEditor.setCursorPos({ line: 7, ch: 5 }); // cursor after 'bord' selectHint(CSSCodeHints.cssPropHintProvider, "border"); expect(testDocument.getLine(7)).toBe(" border: "); expectCursorAt({ line: 7, ch: 9 }); }); it("should not insert semicolon after prop-value selected", function () { testDocument.replaceRange(";", { line: 12, ch: 5 }); testEditor.setCursorPos({ line: 13, ch: 10 }); // cursor after 'display: ' selectHint(CSSCodeHints.cssPropHintProvider, "block"); expect(testDocument.getLine(13)).toBe(" display: block"); }); it("should insert prop-name directly after semicolon", function () { testEditor.setCursorPos({ line: 10, ch: 19 }); // cursor after red; selectHint(CSSCodeHints.cssPropHintProvider, "align-content"); expect(testDocument.getLine(10)).toBe(" border-color: red;align-content: "); }); it("should insert nothing but the closure(semicolon) if prop-value is fully written", function () { testDocument.replaceRange(";", { line: 15, ch: 13 }); // insert text ; testEditor.setCursorPos({ line: 16, ch: 6 }); // cursor directly after color selectHint(CSSCodeHints.cssPropHintProvider, "color"); expect(testDocument.getLine(16)).toBe(" color: "); expectCursorAt({ line: 16, ch: 8 }); }); it("should insert prop-name before an existing one", function () { testEditor.setCursorPos({ line: 10, ch: 1 }); // cursor before border-color: selectHint(CSSCodeHints.cssPropHintProvider, "float"); expect(testDocument.getLine(10)).toBe(" float: border-color: red;"); expectCursorAt({ line: 10, ch: 8 }); }); it("should insert prop-name before an existing one when invoked with an implicit character", function () { testDocument.replaceRange("f", { line: 10, ch: 1 }); // insert "f" before border-color: testEditor.setCursorPos({ line: 10, ch: 2 }); // set cursor before border-color: selectHint(CSSCodeHints.cssPropHintProvider, "float", "f"); expect(testDocument.getLine(10)).toBe(" float: border-color: red;"); expectCursorAt({ line: 10, ch: 8 }); }); it("should replace the existing prop-value with the new selection", function () { testDocument.replaceRange(";", { line: 12, ch: 5 }); testDocument.replaceRange("block", { line: 13, ch: 10 }); testEditor.setCursorPos({ line: 13, ch: 10 }); // cursor before block selectHint(CSSCodeHints.cssPropHintProvider, "none"); expect(testDocument.getLine(13)).toBe(" display: none"); expectCursorAt({ line: 13, ch: 14 }); }); xit("should start new hinting whenever there is a whitespace last stringliteral", function () { // topic: multi-value properties // this needs to be discussed, whether or not this behaviour is aimed for // if so, changes to CSSUtils.getInfoAt need to be done imho to classify this testDocument.replaceRange(" ", { line: 16, ch: 6 }); // insert whitespace after color testEditor.setCursorPos({ line: 16, ch: 7 }); // cursor one whitespace after color selectHint(CSSCodeHints.cssPropHintProvider, "color"); expect(testDocument.getLine(16)).toBe(" color color:"); expectCursorAt({ line: 16, ch: 13 }); }); }); describe("CSS prop-value hints", function () { beforeEach(function () { // create Editor instance (containing a CodeMirror instance) var mock = SpecRunnerUtils.createMockEditor(defaultContent, "css"); testEditor = mock.editor; testDocument = mock.doc; }); afterEach(function () { SpecRunnerUtils.destroyMockEditor(testDocument); testEditor = null; testDocument = null; }); it("should list all prop-values for 'display' after colon", function () { // insert semicolon after previous rule to avoid incorrect tokenizing testDocument.replaceRange(";", { line: 12, ch: 5 }); testEditor.setCursorPos({ line: 13, ch: 9 }); var hintList = expectHints(CSSCodeHints.cssPropHintProvider); verifyAttrHints(hintList, "block"); // filtered after "display:" }); it("should list all prop-values for 'display' after colon and whitespace", function () { // insert semicolon after previous rule to avoid incorrect tokenizing testDocument.replaceRange(";", { line: 12, ch: 5 }); testEditor.setCursorPos({ line: 13, ch: 10 }); var hintList = expectHints(CSSCodeHints.cssPropHintProvider); verifyAttrHints(hintList, "block"); // filtered after "display: " }); it("should list all prop-values starting with 'in' for 'display' after colon and whitespace", function () { // insert semicolon after previous rule to avoid incorrect tokenizing testDocument.replaceRange(";", { line: 13, ch: 10 }); testEditor.setCursorPos({ line: 14, ch: 12 }); var hintList = expectHints(CSSCodeHints.cssPropHintProvider); verifyAttrHints(hintList, "inherit"); // filtered after "display: in" }); it("should NOT list prop-value hints for unknown prop-name", function () { testEditor.setCursorPos({ line: 15, ch: 12 }); // at bordborder: expectNoHints(CSSCodeHints.cssPropHintProvider); }); }); describe("CSS hint provider inside mixed htmlfiles", function () { var defaultContent = " \n" + " \n" + " \n" + "
      \n" + "\n" + ""; beforeEach(function () { // create dummy Document for the Editor var mock = SpecRunnerUtils.createMockEditor(defaultContent, "html"); testEditor = mock.editor; testDocument = mock.doc; }); afterEach(function () { SpecRunnerUtils.destroyMockEditor(testDocument); testEditor = null; testDocument = null; }); it("should list prop-name hints right after curly bracket", function () { testEditor.setCursorPos({ line: 3, ch: 7 }); // inside body-selector, after { expectHints(CSSCodeHints.cssPropHintProvider); }); it("should list prop-name hints inside single-line styletags at start", function () { testEditor.setCursorPos({ line: 1, ch: 23 }); // inside style, after { expectHints(CSSCodeHints.cssPropHintProvider); }); it("should list prop-name hints inside single-line styletags after semicolon", function () { testEditor.setCursorPos({ line: 1, ch: 37 }); // inside style, after ; expectHints(CSSCodeHints.cssPropHintProvider); }); it("should list prop-name hints inside multi-line styletags with cursor in first line", function () { testEditor.setCursorPos({ line: 9, ch: 18 }); // inside style, after { expectHints(CSSCodeHints.cssPropHintProvider); }); it("should list prop-name hints inside multi-line styletags with cursor in last line", function () { testEditor.setCursorPos({ line: 10, ch: 5 }); // inside style, after colo var hintList = expectHints(CSSCodeHints.cssPropHintProvider); verifyListsAreIdentical(hintList, ["color", "border-color", "background-color", "border-left-color", "border-top-color", "outline-color", "border-bottom-color", "border-right-color", "text-decoration-color", "text-emphasis-color", "column-count", "column-rule-color", "background-blend-mode"]); }); it("should NOT list prop-name hints between closed styletag and new opening styletag", function () { testEditor.setCursorPos({ line: 8, ch: 0 }); // right before
      and { expectNoHints(CSSCodeHints.cssPropHintProvider); }); }); describe("CSS Hint provider in style attribute value context for html mode", function () { beforeEach(function () { // create Editor instance (containing a CodeMirror instance) var mock = SpecRunnerUtils.createMockEditor(defaultHTMLContent, "html"); testEditor = mock.editor; testDocument = mock.doc; }); afterEach(function () { SpecRunnerUtils.destroyMockEditor(testDocument); testEditor = null; testDocument = null; }); it("should list all prop-name hints right after the open quote for style value context", function () { testEditor.setCursorPos({ line: 4, ch: 12 }); // after "='" var hintList = expectHints(CSSCodeHints.cssPropHintProvider); verifyAttrHints(hintList, "align-content"); // filtered on "empty string" }); it("should list all prop-name hints in new line for style value context", function () { testEditor.setCursorPos({ line: 5, ch: 0 }); var hintList = expectHints(CSSCodeHints.cssPropHintProvider); verifyAttrHints(hintList, "align-content"); // filtered on "empty string" }); it("should list all prop-name hints starting with 'b' in new line for style value context", function () { testEditor.setCursorPos({ line: 6, ch: 2 }); var hintList = expectHints(CSSCodeHints.cssPropHintProvider); verifyAttrHints(hintList, "backface-visibility"); // filtered on "b" }); it("should list all prop-name hints starting with 'bord' for style value context", function () { // insert semicolon after previous rule to avoid incorrect tokenizing testDocument.replaceRange(";", { line: 6, ch: 2 }); testEditor.setCursorPos({ line: 7, ch: 5 }); var hintList = expectHints(CSSCodeHints.cssPropHintProvider); verifyAttrHints(hintList, "border"); // filtered on "bord" }); it("should list all prop-name hints starting with 'border-' for style value context", function () { // insert semicolon after previous rule to avoid incorrect tokenizing testDocument.replaceRange(";", { line: 7, ch: 5 }); testEditor.setCursorPos({ line: 8, ch: 8 }); var hintList = expectHints(CSSCodeHints.cssPropHintProvider); verifyAttrHints(hintList, "border-bottom"); // filtered on "border-" }); it("should list only prop-name hint border-color for style value context", function () { // insert semicolon after previous rule to avoid incorrect tokenizing testDocument.replaceRange(";", { line: 8, ch: 8 }); testEditor.setCursorPos({ line: 9, ch: 12 }); var hintList = expectHints(CSSCodeHints.cssPropHintProvider); verifyAttrHints(hintList, "border-color"); // filtered on "border-color" verifyListsAreIdentical(hintList, ["border-color", "border-left-color", "border-top-color", "border-bottom-color", "border-right-color"]); }); it("should list prop-name hints at end of property-value finished by ; for style value context", function () { testEditor.setCursorPos({ line: 10, ch: 19 }); // after ; var hintList = expectHints(CSSCodeHints.cssPropHintProvider); verifyAttrHints(hintList, "align-content"); // filtered on "empty string" }); it("should NOT list prop-name hints right before style value context", function () { testEditor.setCursorPos({ line: 4, ch: 11 }); // after = expectNoHints(CSSCodeHints.cssPropHintProvider); }); it("should NOT list prop-name hints after style value context", function () { testEditor.setCursorPos({ line: 10, ch: 20 }); // after "'" expectNoHints(CSSCodeHints.cssPropHintProvider); }); }); describe("CSS hint provider in other filecontext (e.g. javascript)", function () { var defaultContent = "function foobar (args) { \n " + " /* do sth */ \n" + " return 1; \n" + "} \n"; beforeEach(function () { // create dummy Document for the Editor var mock = SpecRunnerUtils.createMockEditor(defaultContent, "javascript"); testEditor = mock.editor; testDocument = mock.doc; }); afterEach(function () { SpecRunnerUtils.destroyMockEditor(testDocument); testEditor = null; testDocument = null; }); it("should NOT list hints after function declaration", function () { testEditor.setCursorPos({ line: 0, ch: 24 }); // after { after function declaration expectNoHints(CSSCodeHints.cssPropHintProvider); }); }); describe("CSS hint provider cursor placement inside value functions", function () { var defaultContent = ".selector { \n" + // line 0 "shape-inside:\n" + // line 1 "}\n"; // line 2 beforeEach(function () { // create dummy Document for the Editor var mock = SpecRunnerUtils.createMockEditor(defaultContent, "css"); testEditor = mock.editor; testDocument = mock.doc; }); afterEach(function () { SpecRunnerUtils.destroyMockEditor(testDocument); testEditor = null; testDocument = null; }); it("should should place the cursor between the parens of the value function", function () { var expectedString = "shape-inside:polygon()"; testEditor.setCursorPos({ line: 1, ch: 15 }); // after shape-inside expectHints(CSSCodeHints.cssPropHintProvider); selectHint(CSSCodeHints.cssPropHintProvider, "polygon()"); expect(testDocument.getLine(1).length).toBe(expectedString.length); expect(testDocument.getLine(1)).toBe(expectedString); expectCursorAt({ line: 1, ch: expectedString.length - 1 }); }); }); describe("CSS hint provider for regions and exclusions", function () { var defaultContent = ".selector { \n" + // line 0 " shape-inside: \n;" + // line 1 " shape-outside: \n;" + // line 2 " region-fragment: \n;" + // line 3 " region-break-after: \n;" + // line 4 " region-break-inside: \n;" + // line 5 " region-break-before: \n;" + // line 6 " -ms-region\n;" + // line 7 " -webkit-region\n;" + // line 8 " flow-from: \n;" + // line 9 " flow-into: \n;" + // line 10 "}\n"; // line 11 beforeEach(function () { // create dummy Document for the Editor var mock = SpecRunnerUtils.createMockEditor(defaultContent, "css"); testEditor = mock.editor; testDocument = mock.doc; }); afterEach(function () { SpecRunnerUtils.destroyMockEditor(testDocument); testEditor = null; testDocument = null; }); it("should list 7 value-name hints for shape-inside", function () { testEditor.setCursorPos({ line: 1, ch: 15 }); // after shape-inside var hintList = expectHints(CSSCodeHints.cssPropHintProvider); verifyAttrHints(hintList, "auto"); // first hint should be auto verifyAllValues(hintList, ["auto", "circle()", "ellipse()", "inherit", "outside-shape", "polygon()", "rectangle()"]); }); it("should list 16 value-name hints for shape-outside", function () { testEditor.setCursorPos({ line: 2, ch: 16 }); // after shape-outside var hintList = expectHints(CSSCodeHints.cssPropHintProvider); verifyAttrHints(hintList, "border-box"); // first hint should be border-box verifyAllValues(hintList, ["none", "inherit", "circle()", "ellipse()", "polygon()", "inset()", "margin-box", "border-box", "padding-box", "content-box", "url()", "image()", "linear-gradient()", "radial-gradient()", "repeating-linear-gradient()", "repeating-radial-gradient()"]); }); it("should list 2 value-name hints for region-fragment", function () { testEditor.setCursorPos({ line: 3, ch: 18 }); // after region-fragment var hintList = expectHints(CSSCodeHints.cssPropHintProvider); verifyAttrHints(hintList, "auto"); // first hint should be auto verifyAllValues(hintList, ["auto", "break"]); }); it("should list 11 value-name hints for region-break-after", function () { testEditor.setCursorPos({ line: 4, ch: 21 }); // after region-break-after var hintList = expectHints(CSSCodeHints.cssPropHintProvider); verifyAttrHints(hintList, "always"); // first hint should be always verifyAllValues(hintList, ["always", "auto", "avoid", "avoid-column", "avoid-page", "avoid-region", "column", "left", "page", "region", "right"]); }); it("should list 5 value-name hints for region-break-inside", function () { testEditor.setCursorPos({ line: 5, ch: 22 }); // after region-break-inside var hintList = expectHints(CSSCodeHints.cssPropHintProvider); verifyAttrHints(hintList, "auto"); // first hint should be auto verifyAllValues(hintList, ["auto", "avoid", "avoid-column", "avoid-page", "avoid-region"]); }); it("should list 11 value-name hints for region-break-before", function () { testEditor.setCursorPos({ line: 6, ch: 23 }); // after region-break-before var hintList = expectHints(CSSCodeHints.cssPropHintProvider); verifyAttrHints(hintList, "always"); // first hint should be always verifyAllValues(hintList, ["always", "auto", "avoid", "avoid-column", "avoid-page", "avoid-region", "column", "left", "page", "region", "right"]); }); // TODO: Need to add vendor prefixed properties for CSS code hint provider. xit("should list 4 value-name hints for vendor prefixed region-* properties", function () { testEditor.setCursorPos({ line: 7, ch: 16 }); // after -ms-region var hintList = expectHints(CSSCodeHints.cssPropHintProvider); verifyAttrHints(hintList, "region-break-after"); // first hint should be region-break-after verifyAllValues(hintList, ["region-break-after", "region-break-before", "region-break-inside", "region-fragment"]); testEditor.setCursorPos({ line: 8, ch: 20 }); // after -webkit-region hintList = expectHints(CSSCodeHints.cssPropHintProvider); verifyAttrHints(hintList, "region-break-after"); // first hint should be region-break-after verifyAllValues(hintList, ["region-break-after", "region-break-before", "region-break-inside", "region-fragment"]); }); it("should list 2 value-name hints for flow-from", function () { testEditor.setCursorPos({ line: 9, ch: 12 }); // after flow-from var hintList = expectHints(CSSCodeHints.cssPropHintProvider); verifyAttrHints(hintList, "inherit"); // first hint should be inherit verifyAllValues(hintList, ["inherit", "none"]); }); it("should list 1 value-name hint for flow-into", function () { testEditor.setCursorPos({ line: 10, ch: 12 }); // after flow-into var hintList = expectHints(CSSCodeHints.cssPropHintProvider); verifyAttrHints(hintList, "none"); // first hint should be none verifyAllValues(hintList, ["none"]); }); }); describe("Named flow hints for flow-into and flow-from properties in a CSS file", function () { beforeEach(function () { setupTest(testContentCSS, "css"); }); afterEach(function () { tearDownTest(); }); it("should list more than 2 value hints for flow-from", function () { testEditor.setCursorPos({ line: 66, ch: 15 }); // after flow-from var hintList = expectHints(CSSCodeHints.cssPropHintProvider); verifyAttrHints(hintList, "edge-code_now_shipping"); // first hint should be edge-code_now_shipping verifyAllValues(hintList, ["edge-code_now_shipping", "inherit", "jeff", "lim", "main", "none", "randy"]); }); it("should list more than 1 value hint for flow-into", function () { testEditor.setCursorPos({ line: 77, ch: 4 }); selectHint(CSSCodeHints.cssPropHintProvider, "flow-into"); expect(testDocument.getLine(77)).toBe(" flow-into: "); expectCursorAt({ line: 77, ch: 15 }); var hintList = expectHints(CSSCodeHints.cssPropHintProvider); verifyAttrHints(hintList, "edge-code_now_shipping"); // first hint should be edge-code_now_shipping verifyAllValues(hintList, ["edge-code_now_shipping", "jeff", "lim", "main", "none", "randy"]); }); it("should NOT include partially entered named flow value in hint list", function () { // Insert a letter for a new named flow after flow-from: on line 66 testDocument.replaceRange("m", { line: 66, ch: 15 }); testEditor.setCursorPos({ line: 66, ch: 16 }); // after flow-from: m var hintList = expectHints(CSSCodeHints.cssPropHintProvider); verifyListsAreIdentical(hintList, ["main", "lim"]); }); }); describe("Named flow hints inside a style block of an HTML", function () { beforeEach(function () { setupTest(testContentHTML, "html"); }); afterEach(function () { tearDownTest(); }); it("should include only 2 named flows available in the style block for flow-from", function () { testEditor.setCursorPos({ line: 28, ch: 21 }); // after flow-from var hintList = expectHints(CSSCodeHints.cssPropHintProvider); verifyAttrHints(hintList, "article"); // first hint should be article verifyAllValues(hintList, ["article", "inherit", "none", "regionC"]); }); it("should include only 2 named flows available in the style block for flow-into", function () { testEditor.setCursorPos({ line: 34, ch: 21 }); var hintList = expectHints(CSSCodeHints.cssPropHintProvider); verifyAttrHints(hintList, "article"); // first hint should be article verifyAllValues(hintList, ["article", "none", "regionC"]); }); it("should NOT include partially entered named flow value in hint list", function () { // Insert a letter for a new named flow after flow-from: on line 28 testDocument.replaceRange("m", { line: 28, ch: 21 }); testEditor.setCursorPos({ line: 28, ch: 22 }); // after flow-from: m var hintList = expectHints(CSSCodeHints.cssPropHintProvider); verifyAllValues(hintList, []); }); it("should NOT show named flow available inisde HTML text", function () { // Insert a letter for a new named flow after flow-from: on line 28 testDocument.replaceRange("some", { line: 28, ch: 21 }); testEditor.setCursorPos({ line: 28, ch: 25 }); // after flow-from: some var hintList = expectHints(CSSCodeHints.cssPropHintProvider); // some-named-flow should not be in the hint list since it is inside HTML text verifyAllValues(hintList, []); }); }); describe("Color names and swatches in a CSS file", function () { beforeEach(function () { setupTest(testContentCSS, "css"); }); afterEach(function () { tearDownTest(); }); it("should list color names for color", function () { testEditor.setCursorPos({ line: 98, ch: 11 }); // after color var hintList = expectHints(CSSCodeHints.cssPropHintProvider); verifyAttrHints(hintList, "aliceblue"); // first hint should be aliceblue }); it("should show color swatches for background-color", function () { testEditor.setCursorPos({ line: 99, ch: 22 }); // after background-color var hints = expectHints(CSSCodeHints.cssPropHintProvider, undefined, true).hints; expect(hints[0].text()).toBe("aliceblue"); // first hint should be aliceblue expect(hints[0].find(".color-swatch").length).toBe(1); // CEF 2623 will output "aliceblue" whereas earlier versions give "rgb(240, 248, 255)", // so we need this ugly hack to make sure this test passes on both expect(hints[0].find(".color-swatch").css("backgroundColor")).toMatch(/^rgb\(240, 248, 255\)$|aliceblue/); }); it("should filter out color names appropriately", function () { testEditor.setCursorPos({ line: 100, ch: 27 }); // after border-left-color var hintList = expectHints(CSSCodeHints.cssPropHintProvider); verifyAttrHints(hintList, "deeppink"); // first hint should be deeppink verifyAllValues(hintList, ["deeppink", "deepskyblue"]); }); it("should always include transparent and currentColor and they should not have a swatch, but class no-swatch-margin", function () { testEditor.setCursorPos({ line: 101, ch: 22 }); // after border-color var hints = expectHints(CSSCodeHints.cssPropHintProvider, undefined, true).hints, hintList = extractHintList(hints); verifyAttrHints(hintList, "currentColor"); // first hint should be currentColor verifyAllValues(hintList, ["currentColor", "darkmagenta", "transparent"]); expect(hints[0].find(".color-swatch").length).toBe(0); // no swatch for currentColor expect(hints[2].find(".color-swatch").length).toBe(0); // no swatch for transparent expect(hints[0].hasClass("no-swatch-margin")).toBeTruthy(); // no-swatch-margin applied to currentColor expect(hints[2].hasClass("no-swatch-margin")).toBeTruthy(); // no-swatch-margin applied to transparent }); it("should remove class no-swatch-margin from transparent if it's the only one in the list", function () { testEditor.setCursorPos({ line: 103, ch: 22 }); // after color var hints = expectHints(CSSCodeHints.cssPropHintProvider, undefined, true).hints, hintList = extractHintList(hints); verifyAllValues(hintList, ["transparent"]); expect(hints[0].find(".color-swatch").length).toBe(0); // no swatch for transparent expect(hints[0].hasClass("no-swatch-margin")).toBeFalsy(); // no-swatch-margin not applied to transparent }); it("should insert color names correctly", function () { var expectedString = " border-left-color: deeppink;", line = 100; testEditor.setCursorPos({ line: line, ch: 27 }); // after border-left-color expectHints(CSSCodeHints.cssPropHintProvider); selectHint(CSSCodeHints.cssPropHintProvider, "deeppink"); expect(testDocument.getLine(line).length).toBe(expectedString.length); expect(testDocument.getLine(line)).toBe(expectedString); expectCursorAt({ line: line, ch: expectedString.length - 1 }); }); it("should not display color names for unrelated properties", function () { testEditor.setCursorPos({ line: 102, ch: 12 }); // after height var hintList = expectHints(CSSCodeHints.cssPropHintProvider); expect(hintList.indexOf("aliceblue")).toBe(-1); }); }); describe("Should not invoke CSS hints on space key", function () { beforeEach(function () { setupTest(testContentHTML, "html"); }); afterEach(function () { tearDownTest(); }); it("should not trigger CSS property name hints with space key", function () { testEditor.setCursorPos({ line: 25, ch: 11 }); // after { expectNoHints(CSSCodeHints.cssPropHintProvider, " "); }); it("should not trigger CSS property value hints with space key", function () { testEditor.setCursorPos({ line: 28, ch: 21 }); // after flow-from expectNoHints(CSSCodeHints.cssPropHintProvider, " "); }); }); }); }); ================================================ FILE: src/extensions/default/CSSPseudoSelectorHints/PseudoSelectors.json ================================================ { "classes": { "active": {"desc": "Selects the link being pressed"}, "any-link": {"desc": "Selects every hyperlink, e.g., , and with href attribute"}, "checked": {"desc": "Selects every checked checkbox"}, "default": {"desc": "Selects every UI element that is the default among a group of similar elements"}, "dir(direction)": {"desc": "Selects every element whose text direction is 'direction'", "text": "dir()"}, "disabled": {"desc": "Selects every disabled form element"}, "empty": {"desc": "Selects every element that has no any child nodes"}, "enabled": {"desc": "Selects every enabled form element"}, "first": {"desc": "With @page, selects the first page of a printed document"}, "first-child": {"desc": "Selects every element that is the first child of its parent"}, "first-of-type": {"desc": "Selects every element that is the first element of the specific type of its parent"}, "focus": {"desc": "Selects the input element which has focus"}, "focus-within": {"desc": "Selects every element which or whose descendant has focus"}, "fullscreen": {"desc": "Selects the element being in fullscreen mode"}, "hover": {"desc": "Selects elements on pointer over"}, "in-range": {"desc": "Selects input elements with a value within a specified range"}, "indeterminate": {"desc": "Selects every indeterminate checkbox, radio button or progress bar"}, "invalid": {"desc": "Selects all input elements with an invalid value"}, "lang(languages)": {"desc": "Selects every element whose language is contained by the 'languages' list", "text": "lang()"}, "last-child": {"desc": "Selects every element that is the last child of its parent"}, "last-of-type": {"desc": "Selects every element that is the last element of the specific type of its parent"}, "left": {"desc": "With @page, selects every left-hand page of a printed document"}, "link": {"desc": "Selects all unvisited links"}, "matches(selectors)": {"desc": "Selects every element that is matched by one or more selectors in the 'selectors' list", "text": "matches()"}, "not(selectors)": {"desc": "Selects every element that is not matched by any selector in the 'selectors' list", "text": "not()"}, "nth-child(n)": {"desc": "Selects every element that is the nth child of its parent", "text": "nth-child()"}, "nth-last-child(n)": {"desc": "Selects every element that is the nth child of its parent, counting from the last child", "text": "nth-last-child()"}, "nth-last-of-type(n)": {"desc": "Selects every element that is the nth element of the specific type of its parent, counting from the last child", "text": "nth-last-of-type()"}, "nth-of-type(n)": {"desc": "Selects every element that is the nth element of the specific type of its parent", "text": "nth-of-type(n)"}, "only-child": {"desc": "Selects every element that is the only child of its parent"}, "only-of-type": {"desc": "Selects every element that is the only element of the specific type of its parent"}, "optional": {"desc": "Selects non-required form elements"}, "out-of-range": {"desc": "Selects input elements with a value outside a specified range"}, "placeholder-shown": {"desc": "Selects all and
      ================================================ FILE: src/extensions/default/DebugCommands/keyboard.json ================================================ { "showDeveloperTools": [ { "key": "F12" }, { "key": "Cmd-Opt-I", "platform": "mac" } ], "refreshWindow": [ { "key": "F5" }, { "key": "Cmd-R", "platform": "mac" } ], "reloadWithoutUserExts": [ { "key": "Shift-F5" }, { "key": "Cmd-Ctrl-R", "platform": "mac" } ] } ================================================ FILE: src/extensions/default/DebugCommands/main.js ================================================ /* * Copyright (c) 2012 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /*jslint regexp: true */ define(function (require, exports, module) { "use strict"; var _ = brackets.getModule("thirdparty/lodash"); var Commands = brackets.getModule("command/Commands"), CommandManager = brackets.getModule("command/CommandManager"), Menus = brackets.getModule("command/Menus"), FileSystem = brackets.getModule("filesystem/FileSystem"), FileUtils = brackets.getModule("file/FileUtils"), PerfUtils = brackets.getModule("utils/PerfUtils"), StringUtils = brackets.getModule("utils/StringUtils"), Dialogs = brackets.getModule("widgets/Dialogs"), Strings = brackets.getModule("strings"), PreferencesManager = brackets.getModule("preferences/PreferencesManager"), LocalizationUtils = brackets.getModule("utils/LocalizationUtils"), MainViewManager = brackets.getModule("view/MainViewManager"), WorkingSetView = brackets.getModule("project/WorkingSetView"), ExtensionManager = brackets.getModule("extensibility/ExtensionManager"), Mustache = brackets.getModule("thirdparty/mustache/mustache"), ErrorNotification = require("ErrorNotification"), NodeDebugUtils = require("NodeDebugUtils"), PerfDialogTemplate = require("text!htmlContent/perf-dialog.html"), LanguageDialogTemplate = require("text!htmlContent/language-dialog.html"); var KeyboardPrefs = JSON.parse(require("text!keyboard.json")); // default preferences file name var DEFAULT_PREFERENCES_FILENAME = "defaultPreferences.json", SUPPORTED_PREFERENCE_TYPES = ["number", "boolean", "string", "array", "object"]; var recomputeDefaultPrefs = true, defaultPreferencesFullPath = brackets.app.getApplicationSupportDirectory() + "/" + DEFAULT_PREFERENCES_FILENAME; /** * Brackets Application Menu Constant * @const {string} */ var DEBUG_MENU = "debug-menu"; /** * Debug commands IDs * @enum {string} */ var DEBUG_REFRESH_WINDOW = "debug.refreshWindow", // string must MATCH string in native code (brackets_extensions) DEBUG_SHOW_DEVELOPER_TOOLS = "debug.showDeveloperTools", DEBUG_RUN_UNIT_TESTS = "debug.runUnitTests", DEBUG_SHOW_PERF_DATA = "debug.showPerfData", DEBUG_RELOAD_WITHOUT_USER_EXTS = "debug.reloadWithoutUserExts", DEBUG_NEW_BRACKETS_WINDOW = "debug.newBracketsWindow", DEBUG_SWITCH_LANGUAGE = "debug.switchLanguage", DEBUG_ENABLE_NODE_DEBUGGER = "debug.enableNodeDebugger", DEBUG_LOG_NODE_STATE = "debug.logNodeState", DEBUG_RESTART_NODE = "debug.restartNode", DEBUG_SHOW_ERRORS_IN_STATUS_BAR = "debug.showErrorsInStatusBar", DEBUG_OPEN_BRACKETS_SOURCE = "debug.openBracketsSource", DEBUG_OPEN_PREFERENCES_IN_SPLIT_VIEW = "debug.openPrefsInSplitView"; // define a preference to turn off opening preferences in split-view. var prefs = PreferencesManager.getExtensionPrefs("preferencesView"); prefs.definePreference("openPrefsInSplitView", "boolean", true, { description: Strings.DESCRIPTION_OPEN_PREFS_IN_SPLIT_VIEW }); prefs.definePreference("openUserPrefsInSecondPane", "boolean", true, { description: Strings.DESCRIPTION_OPEN_USER_PREFS_IN_SECOND_PANE }); PreferencesManager.definePreference(DEBUG_SHOW_ERRORS_IN_STATUS_BAR, "boolean", false, { description: Strings.DESCRIPTION_SHOW_ERRORS_IN_STATUS_BAR }); function handleShowDeveloperTools() { brackets.app.showDeveloperTools(); } // Implements the 'Run Tests' menu to bring up the Jasmine unit test window var _testWindow = null; function _runUnitTests(spec) { var queryString = spec ? "?spec=" + spec : ""; if (_testWindow && !_testWindow.closed) { if (_testWindow.location.search !== queryString) { _testWindow.location.href = "../test/SpecRunner.html" + queryString; } else { _testWindow.location.reload(true); } } else { _testWindow = window.open("../test/SpecRunner.html" + queryString, "brackets-test", "width=" + $(window).width() + ",height=" + $(window).height()); _testWindow.location.reload(true); // if it had been opened earlier, force a reload because it will be cached } } function handleReload() { CommandManager.execute(Commands.APP_RELOAD); } function handleReloadWithoutUserExts() { CommandManager.execute(Commands.APP_RELOAD_WITHOUT_EXTS); } function handleNewBracketsWindow() { window.open(window.location.href); } function handleShowPerfData() { var templateVars = { delimitedPerfData: PerfUtils.getDelimitedPerfData(), perfData: [] }; var getValue = function (entry) { // entry is either an Array or a number if (Array.isArray(entry)) { // For Array of values, return: minimum/average(count)/maximum/last var i, e, avg, sum = 0, min = Number.MAX_VALUE, max = 0; for (i = 0; i < entry.length; i++) { e = entry[i]; min = Math.min(min, e); sum += e; max = Math.max(max, e); } avg = Math.round(sum * 10 / entry.length) / 10; // tenth of a millisecond return String(min) + "/" + String(avg) + "(" + entry.length + ")/" + String(max) + "/" + String(e); } else { return entry; } }; var perfData = PerfUtils.getData(); _.forEach(perfData, function (value, testName) { templateVars.perfData.push({ testName: StringUtils.breakableUrl(testName), value: getValue(value) }); }); var template = Mustache.render(PerfDialogTemplate, templateVars); Dialogs.showModalDialogUsingTemplate(template); // Select the raw perf data field on click since select all doesn't // work outside of the editor $("#brackets-perf-raw-data").click(function () { $(this).focus().select(); }); } function handleSwitchLanguage() { var stringsPath = FileUtils.getNativeBracketsDirectoryPath() + "/nls"; FileSystem.getDirectoryForPath(stringsPath).getContents(function (err, entries) { if (!err) { var $dialog, $submit, $select, locale, curLocale = (brackets.isLocaleDefault() ? null : brackets.getLocale()), languages = []; var setLanguage = function (event) { locale = $select.val(); $submit.prop("disabled", locale === (curLocale || "")); }; // inspect all children of dirEntry entries.forEach(function (entry) { if (entry.isDirectory) { var match = entry.name.match(/^([a-z]{2})(-[a-z]{2})?$/); if (match) { var language = entry.name, label = match[1]; if (match[2]) { label += match[2].toUpperCase(); } languages.push({label: LocalizationUtils.getLocalizedLabel(label), language: language}); } } }); // add English (US), which is the root folder and should be sorted as well languages.push({label: LocalizationUtils.getLocalizedLabel("en"), language: "en"}); // sort the languages via their display name languages.sort(function (lang1, lang2) { return lang1.label.localeCompare(lang2.label); }); // add system default (which is placed on the very top) languages.unshift({label: Strings.LANGUAGE_SYSTEM_DEFAULT, language: null}); var template = Mustache.render(LanguageDialogTemplate, {languages: languages, Strings: Strings}); Dialogs.showModalDialogUsingTemplate(template).done(function (id) { if (id === Dialogs.DIALOG_BTN_OK && locale !== curLocale) { brackets.setLocale(locale); CommandManager.execute(Commands.APP_RELOAD); } }); $dialog = $(".switch-language.instance"); $submit = $dialog.find(".dialog-button[data-button-id='" + Dialogs.DIALOG_BTN_OK + "']"); $select = $dialog.find("select"); $select.on("change", setLanguage).val(curLocale); } }); } function enableRunTestsMenuItem() { if (brackets.inBrowser) { return; } // Check for the SpecRunner.html file var file = FileSystem.getFileForPath( FileUtils.getNativeBracketsDirectoryPath() + "/../test/SpecRunner.html" ); file.exists(function (err, exists) { if (!err && exists) { // If the SpecRunner.html file exists, enable the menu item. // (menu item is already disabled, so no need to disable if the // file doesn't exist). CommandManager.get(DEBUG_RUN_UNIT_TESTS).setEnabled(true); } }); } function toggleErrorNotification(bool) { var val, oldPref = !!PreferencesManager.get(DEBUG_SHOW_ERRORS_IN_STATUS_BAR); if (bool === undefined) { val = !oldPref; } else { val = !!bool; } ErrorNotification.toggle(val); // update menu CommandManager.get(DEBUG_SHOW_ERRORS_IN_STATUS_BAR).setChecked(val); if (val !== oldPref) { PreferencesManager.set(DEBUG_SHOW_ERRORS_IN_STATUS_BAR, val); } } function handleOpenBracketsSource() { // Brackets source dir w/o the trailing src/ folder var dir = FileUtils.getNativeBracketsDirectoryPath().replace(/\/[^\/]+$/, "/"); brackets.app.showOSFolder(dir); } function _openPrefFilesInSplitView(prefsPath, defaultPrefsPath, deferredPromise) { var currScheme = MainViewManager.getLayoutScheme(), file = FileSystem.getFileForPath(prefsPath), defaultPrefsFile = FileSystem.getFileForPath(defaultPrefsPath), DEFAULT_PREFS_PANE = "first-pane", USER_PREFS_PANE = "second-pane"; // Exchange the panes, if default preferences need to be opened // in the right pane. if (!prefs.get("openUserPrefsInSecondPane")) { DEFAULT_PREFS_PANE = "second-pane"; USER_PREFS_PANE = "first-pane"; } function _openFiles() { if (currScheme.rows === 1 && currScheme.columns === 1) { // Split layout is not active yet. Initiate the // split view. MainViewManager.setLayoutScheme(1, 2); } // Open the default preferences in the left pane in the read only mode. CommandManager.execute(Commands.FILE_OPEN, { fullPath: defaultPrefsPath, paneId: DEFAULT_PREFS_PANE, options: { isReadOnly: true } }) .done(function () { // Make sure the preference file is going to be opened in pane // specified in the preference. if (MainViewManager.findInWorkingSet(DEFAULT_PREFS_PANE, prefsPath) >= 0) { MainViewManager._moveView(DEFAULT_PREFS_PANE, USER_PREFS_PANE, file, 0, true); // Now refresh the project tree by asking // it to rebuild the UI. WorkingSetView.refresh(true); } CommandManager.execute(Commands.FILE_OPEN, { fullPath: prefsPath, paneId: USER_PREFS_PANE}) .done(function () { deferredPromise.resolve(); }).fail(function () { deferredPromise.reject(); }); }).fail(function () { deferredPromise.reject(); }); } var resultObj = MainViewManager.findInAllWorkingSets(defaultPrefsPath); if (resultObj && resultObj.length > 0) { CommandManager.execute(Commands.FILE_CLOSE, {file: defaultPrefsFile, paneId: resultObj[0].paneId}) .done(function () { _openFiles(); }).fail(function () { deferredPromise.reject(); }); } else { _openFiles(); } } function _isSupportedPrefType(prefType) { if (SUPPORTED_PREFERENCE_TYPES.indexOf(prefType) >= 0) { return true; } else { return false; } } /* * This method tries to deduce the preference type * based on various parameters like objects initial * value, object type, object's type property. */ function _getPrefType(prefItem) { var finalPrefType = "undefined"; if (prefItem) { // check the type parameter. var _prefType = prefItem.type; if (_prefType !== undefined) { finalPrefType = prefItem.type.toLowerCase(); // make sure the initial property's // object type matches to that of 'type' property. if (prefItem.initial !== undefined) { if (Array.isArray(prefItem.initial)) { _prefType = "array"; } else { var _initialType = typeof (prefItem.initial); _initialType = _initialType.toLowerCase(); if (_prefType !== _initialType) { _prefType = _initialType; } } } } if (_prefType) { // preference object's type // is defined. Check if that is valid or not. finalPrefType = _prefType; if (!_isSupportedPrefType(finalPrefType)) { finalPrefType = "undefined"; } } else if (Array.isArray(prefItem)) { // Check if the object itself // is an array, in which case // we log the default. finalPrefType = "array"; } else if (prefItem.initial !== undefined || prefItem.keys !== undefined) { // OK looks like this preference has // no explicit type defined. instead // it needs to be deduced from initial/keys // variable. var _prefVar; if (prefItem.initial !== undefined) { _prefVar = prefItem.initial; } else { _prefVar = prefItem.keys; } if (Array.isArray(_prefVar)) { // In cases of array the // typeof is returning a function. finalPrefType = "array"; } } else { finalPrefType = typeof (prefItem); } } // Now make sure we recognize this format. if (!_isSupportedPrefType(finalPrefType)) { finalPrefType = "undefined"; } return finalPrefType; } function _isValidPref(pref) { // Make sure to generate pref description only for // user overrides and don't generate for properties // meant to be used for internal purposes. Also check // if the preference type is valid or not. if (pref && !pref.excludeFromHints && _getPrefType(pref) !== "undefined") { return true; } return false; } /* * This method tries to match between initial objects * and key objects and then aggregates objects from both * the properties. */ function _getChildPrefs(prefItem) { var finalObj = {}, keysFound = false; if (!prefItem) { return {}; } function _populateKeys(allKeys) { var prop; if (typeof (allKeys) === "object") { // iterate through the list. keysFound = true; for (prop in allKeys) { if (allKeys.hasOwnProperty(prop)) { finalObj[prop] = allKeys[prop]; } } } } _populateKeys(prefItem.initial); _populateKeys(prefItem.keys); // Last resort: Maybe plain objects, in which case // we blindly extract all the properties. if (!keysFound) { _populateKeys(prefItem); } return finalObj; } function _formatBasicPref(prefItem, prefName, tabIndentStr) { if (!prefItem || typeof (prefName) !== "string" || _getPrefType(prefItem) === "object") { // return empty string in case of // object or pref is not defined. return ""; } var prefDescription = prefItem.description || "", prefDefault = prefItem.initial, prefFormatText = tabIndentStr + "\t// {0}\n" + tabIndentStr + "\t\"{1}\": {2}", prefItemType = _getPrefType(prefItem); if (prefDefault === undefined && !prefItem.description) { // This could be the case when prefItem is a basic JS variable. if (prefItemType === "number" || prefItemType === "boolean" || prefItemType === "string") { prefDefault = prefItem; } } if (prefDefault === undefined) { if (prefItemType === "number") { prefDefault = 0; } else if (prefItemType === "boolean") { // Defaulting the preference to false, // in case this is missing. prefDefault = false; } else { // for all other types prefDefault = ""; } } if ((prefDescription === undefined || prefDescription.length === 0)) { if (!Array.isArray(prefDefault)) { prefDescription = Strings.DEFAULT_PREFERENCES_JSON_DEFAULT + ": " + prefDefault; } else { prefDescription = ""; } } if (prefItemType === "array") { prefDefault = "[]"; } else if (prefDefault.length === 0 || (prefItemType !== "boolean" && prefItemType !== "number")) { prefDefault = "\"" + prefDefault + "\""; } return StringUtils.format(prefFormatText, prefDescription, prefName, prefDefault); } function _formatPref(prefName, prefItem, indentLevel) { // check for validity of the parameters being passed if (!prefItem || indentLevel < 0 || !prefName || !prefName.length) { return ""; } var iLevel, prefItemKeys, entireText = "", prefItemDesc = prefItem.description || "", prefItemType = _getPrefType(prefItem), hasKeys = false, tabIndents = "", numKeys = 0; // Generate the indentLevel string for (iLevel = 0; iLevel < indentLevel; iLevel++) { tabIndents += "\t"; } // Check if the preference is an object. if (_getPrefType(prefItem) === "object") { prefItemKeys = _getChildPrefs(prefItem); if (Object.keys(prefItemKeys).length > 0) { hasKeys = true; } } // There are some properties like "highlightMatches" that // are declared as boolean type but still can take object keys. // The below condition check can take care of cases like this. if (prefItemType !== "object" && hasKeys === false) { return _formatBasicPref(prefItem, prefName, tabIndents); } // Indent the beginning of the object. tabIndents += "\t"; if (prefItemDesc && prefItemDesc.length > 0) { entireText = tabIndents + "// " + prefItemDesc + "\n"; } entireText += tabIndents + "\"" + prefName + "\": " + "{"; if (prefItemKeys) { numKeys = Object.keys(prefItemKeys).length; } // In case the object array is empty if (numKeys <= 0) { entireText += "}"; return entireText; } else { entireText += "\n"; } // Now iterate through all the keys // and generate nested formatted objects. Object.keys(prefItemKeys).sort().forEach(function (property) { if (prefItemKeys.hasOwnProperty(property)) { var pref = prefItemKeys[property]; if (_isValidPref(pref)) { var formattedText = ""; if (_getPrefType(pref) === "object") { formattedText = _formatPref(property, pref, indentLevel + 1); } else { formattedText = _formatBasicPref(pref, property, tabIndents); } if (formattedText.length > 0) { entireText += formattedText + ",\n\n"; } } } }); // Strip ",\n\n" that got added above, for the last property if (entireText.length > 0) { entireText = entireText.slice(0, -3) + "\n" + tabIndents + "}"; } else { entireText = "{}"; } return entireText; } function _getDefaultPreferencesString() { var allPrefs = PreferencesManager.getAllPreferences(), headerComment = Strings.DEFAULT_PREFERENCES_JSON_HEADER_COMMENT + "\n\n{\n", entireText = ""; Object.keys(allPrefs).sort().forEach(function (property) { if (allPrefs.hasOwnProperty(property)) { var pref = allPrefs[property]; if (_isValidPref(pref)) { entireText += _formatPref(property, pref, 0) + ",\n\n"; } } }); // Strip ",\n\n" that got added above, for the last property if (entireText.length > 0) { entireText = headerComment + entireText.slice(0, -3) + "\n}\n"; } else { entireText = headerComment + "}\n"; } return entireText; } function _loadDefaultPrefs(prefsPath, deferredPromise) { var defaultPrefsPath = defaultPreferencesFullPath, file = FileSystem.getFileForPath(defaultPrefsPath); function _executeDefaultOpenPrefsCommand() { CommandManager.execute(Commands.FILE_OPEN_PREFERENCES) .done(function () { deferredPromise.resolve(); }).fail(function () { deferredPromise.reject(); }); } file.exists(function (err, doesExist) { if (doesExist) { // Go about recreating the default preferences file. if (recomputeDefaultPrefs) { var prefsString = _getDefaultPreferencesString(); recomputeDefaultPrefs = false; // We need to delete this first file.unlink(function (err) { if (!err) { // Go about recreating this // file and write the default // preferences string to this file. FileUtils.writeText(file, prefsString, true) .done(function () { recomputeDefaultPrefs = false; _openPrefFilesInSplitView(prefsPath, defaultPrefsPath, deferredPromise); }).fail(function (error) { // Give a chance for default preferences command. console.error("Unable to write to default preferences file! error code:" + error); _executeDefaultOpenPrefsCommand(); }); } else { // Some error occured while trying to delete // the file. In this case open the user // preferences alone. console.error("Unable to delete the existing default preferences file! error code:" + err); _executeDefaultOpenPrefsCommand(); } }); } else { // Default preferences already generated. // Just go about opening both the files. _openPrefFilesInSplitView(prefsPath, defaultPrefsPath, deferredPromise); } } else { // The default prefs file does not exist at all. // So go about recreating the default preferences // file. var _prefsString = _getDefaultPreferencesString(); FileUtils.writeText(file, _prefsString, true) .done(function () { recomputeDefaultPrefs = false; _openPrefFilesInSplitView(prefsPath, defaultPrefsPath, deferredPromise); }).fail(function (error) { // Give a chance for default preferences command. console.error("Unable to write to default preferences file! error code:" + error); _executeDefaultOpenPrefsCommand(); }); } }); } function handleOpenPrefsInSplitView() { var fullPath = PreferencesManager.getUserPrefFile(), file = FileSystem.getFileForPath(fullPath), splitViewPrefOn = prefs.get("openPrefsInSplitView"), result = new $.Deferred(); if (!splitViewPrefOn) { return CommandManager.execute(Commands.FILE_OPEN_PREFERENCES); } else { file.exists(function (err, doesExist) { if (doesExist) { _loadDefaultPrefs(fullPath, result); } else { FileUtils.writeText(file, "", true) .done(function () { _loadDefaultPrefs(fullPath, result); }).fail(function () { result.reject(); }); } }); } return result.promise(); } ExtensionManager.on("statusChange", function (id) { // Seems like an extension(s) got installed. // Need to recompute the default prefs. recomputeDefaultPrefs = true; }); /* Register all the command handlers */ // Show Developer Tools (optionally enabled) CommandManager.register(Strings.CMD_SHOW_DEV_TOOLS, DEBUG_SHOW_DEVELOPER_TOOLS, handleShowDeveloperTools) .setEnabled(!!brackets.app.showDeveloperTools); CommandManager.register(Strings.CMD_REFRESH_WINDOW, DEBUG_REFRESH_WINDOW, handleReload); CommandManager.register(Strings.CMD_RELOAD_WITHOUT_USER_EXTS, DEBUG_RELOAD_WITHOUT_USER_EXTS, handleReloadWithoutUserExts); CommandManager.register(Strings.CMD_NEW_BRACKETS_WINDOW, DEBUG_NEW_BRACKETS_WINDOW, handleNewBracketsWindow); // Start with the "Run Tests" item disabled. It will be enabled later if the test file can be found. CommandManager.register(Strings.CMD_RUN_UNIT_TESTS, DEBUG_RUN_UNIT_TESTS, _runUnitTests) .setEnabled(false); CommandManager.register(Strings.CMD_SHOW_PERF_DATA, DEBUG_SHOW_PERF_DATA, handleShowPerfData); // Open Brackets Source (optionally enabled) CommandManager.register(Strings.CMD_OPEN_BRACKETS_SOURCE, DEBUG_OPEN_BRACKETS_SOURCE, handleOpenBracketsSource) .setEnabled(!StringUtils.endsWith(decodeURI(window.location.pathname), "/www/index.html")); CommandManager.register(Strings.CMD_SWITCH_LANGUAGE, DEBUG_SWITCH_LANGUAGE, handleSwitchLanguage); CommandManager.register(Strings.CMD_SHOW_ERRORS_IN_STATUS_BAR, DEBUG_SHOW_ERRORS_IN_STATUS_BAR, toggleErrorNotification); // Node-related Commands CommandManager.register(Strings.CMD_ENABLE_NODE_DEBUGGER, DEBUG_ENABLE_NODE_DEBUGGER, NodeDebugUtils.enableDebugger); CommandManager.register(Strings.CMD_LOG_NODE_STATE, DEBUG_LOG_NODE_STATE, NodeDebugUtils.logNodeState); CommandManager.register(Strings.CMD_RESTART_NODE, DEBUG_RESTART_NODE, NodeDebugUtils.restartNode); CommandManager.register(Strings.CMD_OPEN_PREFERENCES, DEBUG_OPEN_PREFERENCES_IN_SPLIT_VIEW, handleOpenPrefsInSplitView); enableRunTestsMenuItem(); toggleErrorNotification(PreferencesManager.get(DEBUG_SHOW_ERRORS_IN_STATUS_BAR)); PreferencesManager.on("change", DEBUG_SHOW_ERRORS_IN_STATUS_BAR, function () { toggleErrorNotification(PreferencesManager.get(DEBUG_SHOW_ERRORS_IN_STATUS_BAR)); }); /* * Debug menu */ var menu = Menus.addMenu(Strings.DEBUG_MENU, DEBUG_MENU, Menus.BEFORE, Menus.AppMenuBar.HELP_MENU); menu.addMenuItem(DEBUG_SHOW_DEVELOPER_TOOLS, KeyboardPrefs.showDeveloperTools); menu.addMenuItem(DEBUG_REFRESH_WINDOW, KeyboardPrefs.refreshWindow); menu.addMenuItem(DEBUG_RELOAD_WITHOUT_USER_EXTS, KeyboardPrefs.reloadWithoutUserExts); menu.addMenuItem(DEBUG_NEW_BRACKETS_WINDOW); menu.addMenuDivider(); menu.addMenuItem(DEBUG_SWITCH_LANGUAGE); menu.addMenuDivider(); menu.addMenuItem(DEBUG_RUN_UNIT_TESTS); menu.addMenuItem(DEBUG_SHOW_PERF_DATA); menu.addMenuItem(DEBUG_OPEN_BRACKETS_SOURCE); menu.addMenuDivider(); menu.addMenuItem(DEBUG_ENABLE_NODE_DEBUGGER); menu.addMenuItem(DEBUG_LOG_NODE_STATE); menu.addMenuItem(DEBUG_RESTART_NODE); menu.addMenuItem(DEBUG_SHOW_ERRORS_IN_STATUS_BAR); menu.addMenuItem(DEBUG_OPEN_PREFERENCES_IN_SPLIT_VIEW); // this command will enable defaultPreferences and brackets preferences to be open side by side in split view. menu.addMenuItem(Commands.FILE_OPEN_KEYMAP); // this command is defined in core, but exposed only in Debug menu for now // exposed for convenience, but not official API exports._runUnitTests = _runUnitTests; }); ================================================ FILE: src/extensions/default/DebugCommands/styles.css ================================================ #error-counter { cursor: pointer; transition: all 3s; color: #f74687; background-color: transparent; } #error-counter.flash { transition: all 1s; background-color: #ffb0cd; } ================================================ FILE: src/extensions/default/HTMLCodeHints/HtmlAttributes.json ================================================ { "accesskey": { "attribOption": [], "global": "true" }, "class": { "attribOption": [], "global": "true", "type": "cssStyle" }, "contenteditable": { "attribOption": [], "global": "true", "type": "boolean" }, "contextmenu": { "attribOption": [], "global": "true" }, "dir": { "attribOption": ["ltr", "rtl"], "global": "true"}, "draggable": { "attribOption": ["auto", "false", "true"], "global": "true" }, "dropzone": { "attribOption": ["copy", "move", "link"], "global": "true" }, "hidden": { "attribOption": [], "type": "flag", "global": "true" }, "id": { "attribOption": [], "global": "true", "type": "cssId" }, "lang": { "attribOption": ["ab", "aa", "af", "sq", "am", "ar", "an", "hy", "as", "ay", "az", "ba", "eu", "bn", "dz", "bh", "bi", "br", "bg", "my", "be", "km", "ca", "zh", "co", "hr", "cs", "da", "nl", "en", "eo", "et", "fo", "fa", "fi", "fr", "fy", "gl", "gd", "gv", "ka", "de", "el", "kl", "gn", "gu", "ht", "ha", "he", "hi", "hu", "is", "io", "id", "ia", "ie", "iu", "ik", "ga", "it", "ja", "jv", "kn", "ks", "kk", "rw", "ky", "rn", "ko", "ku", "lo", "la", "lv", "li", "ln", "lt", "mk", "mg", "ms", "ml", "mt", "mi", "mr", "mo", "mn", "na", "ne", "no", "oc", "or", "om", "ps", "pl", "pt", "pa", "qu", "rm", "ro", "ru", "sz", "sm", "sg", "sa", "sr", "sh", "st", "tn", "sn", "ii", "sd", "si", "ss", "sk", "sl", "so", "es", "su", "sw", "sv", "tl", "tg", "ta", "tt", "te", "th", "bo", "ti", "to", "ts", "tr", "tk", "tw", "ug", "uk", "ur", "uz", "vi", "vo", "wa", "cy", "wo", "xh", "yi", "yo", "zu"], "global": "true" }, "role": { "attribOption": ["alert", "alertdialog", "article", "application", "banner", "button", "checkbox", "columnheader", "combobox", "complementary", "contentinfo", "definition", "directory", "dialog", "document", "form", "grid", "gridcell", "group", "heading", "img", "link", "list", "listbox", "listitem", "log", "main", "marquee", "math", "menu", "menubar", "menuitem", "menuitemcheckbox", "menuitemradio", "navigation", "note", "option", "presentation", "progressbar", "radio", "radiogroup", "region", "row", "rowgroup", "rowheader", "scrollbar", "search", "separator", "slider", "spinbutton", "status", "tab", "tablist", "tabpanel", "textbox", "timer", "toolbar", "tooltip", "tree", "treegrid", "treeitem"], "global": "true" }, "spellcheck": { "attribOption": [], "global": "true", "type": "boolean" }, "style": { "attribOption": [], "global": "true", "type": "style" }, "tabindex": { "attribOption": [], "global": "true" }, "title": { "attribOption": [], "global": "true" }, "onabort": { "attribOption": [], "global": "true" }, "onblur": { "attribOption": [], "global": "true" }, "oncanplay": { "attribOption": [], "global": "true" }, "oncanplaythrough": { "attribOption": [], "global": "true" }, "onchange": { "attribOption": [], "global": "true" }, "onclick": { "attribOption": [], "global": "true" }, "oncontextmenu": { "attribOption": [], "global": "true" }, "oncuechange": { "attribOption": [], "global": "true" }, "ondblclick": { "attribOption": [], "global": "true" }, "ondrag": { "attribOption": [], "global": "true" }, "ondragend": { "attribOption": [], "global": "true" }, "ondragenter": { "attribOption": [], "global": "true" }, "ondragleave": { "attribOption": [], "global": "true" }, "ondragover": { "attribOption": [], "global": "true" }, "ondragstart": { "attribOption": [], "global": "true" }, "ondrop": { "attribOption": [], "global": "true" }, "ondurationchange": { "attribOption": [], "global": "true" }, "onemptied": { "attribOption": [], "global": "true" }, "onended": { "attribOption": [], "global": "true" }, "onerror": { "attribOption": [], "global": "true" }, "onfocus": { "attribOption": [], "global": "true" }, "oninput": { "attribOption": [], "global": "true" }, "oninvalid": { "attribOption": [], "global": "true" }, "onkeydown": { "attribOption": [], "global": "true" }, "onkeypress": { "attribOption": [], "global": "true" }, "onkeyup": { "attribOption": [], "global": "true" }, "onload": { "attribOption": [], "global": "true" }, "onloadeddata": { "attribOption": [], "global": "true" }, "onloadedmetadata": { "attribOption": [], "global": "true" }, "onloadstart": { "attribOption": [], "global": "true" }, "onmousedown": { "attribOption": [], "global": "true" }, "onmousemove": { "attribOption": [], "global": "true" }, "onmouseout": { "attribOption": [], "global": "true" }, "onmouseover": { "attribOption": [], "global": "true" }, "onmouseup": { "attribOption": [], "global": "true" }, "onmousewheel": { "attribOption": [], "global": "true" }, "onpause": { "attribOption": [], "global": "true" }, "onplay": { "attribOption": [], "global": "true" }, "onplaying": { "attribOption": [], "global": "true" }, "onprogress": { "attribOption": [], "global": "true" }, "onratechange": { "attribOption": [], "global": "true" }, "onreadystatechange": { "attribOption": [], "global": "true" }, "onreset": { "attribOption": [], "global": "true" }, "onscroll": { "attribOption": [], "global": "true" }, "onseeked": { "attribOption": [], "global": "true" }, "onseeking": { "attribOption": [], "global": "true" }, "onselect": { "attribOption": [], "global": "true" }, "onshow": { "attribOption": [], "global": "true" }, "onstalled": { "attribOption": [], "global": "true" }, "onsubmit": { "attribOption": [], "global": "true" }, "onsuspend": { "attribOption": [], "global": "true" }, "ontimeupdate": { "attribOption": [], "global": "true" }, "onvolumechange": { "attribOption": [], "global": "true" }, "onwaiting": { "attribOption": [], "global": "true" }, "aria-autocomplete": { "attribOption": ["inline", "list", "both", "none"] }, "aria-activedescendant": { "attribOption": [], "global": "true" }, "aria-atomic": { "attribOption": ["true", "false"], "global": "true", "type": "boolean" }, "aria-busy": { "attribOption": [], "global": "true", "type": "boolean" }, "aria-checked": { "attribOption": ["true", "false", "mixed", "undefined"] }, "aria-controls": { "attribOption": [], "global": "true" }, "aria-describedby": { "attribOption": [], "global": "true" }, "aria-disabled": { "attribOption": ["true", "false"], "global": "true" }, "aria-dropeffect": { "attribOption": ["copy", "move", "link", "execute", "popup", "none"], "global": "true" }, "aria-expanded": { "attribOption": ["true", "false", "undefined"] }, "aria-flowto": { "attribOption": [], "global": "true" }, "aria-grabbed": { "attribOption": ["true", "false", "undefined"], "global": "true" }, "aria-haspopup": { "attribOption": ["true", "false"], "global": "true", "type": "boolean" }, "aria-hidden": { "attribOption": ["true", "false"], "global": "true", "type": "boolean" }, "aria-invalid": { "attribOption": ["grammar", "false", "spelling", "true"], "global": "true" }, "aria-label": { "attribOption": [], "global": "true" }, "aria-labelledby": { "attribOption": [], "global": "true" }, "aria-level": { "attribOption": [] }, "aria-live": { "attribOption": ["off", "polite", "assertive"], "global": "true" }, "aria-multiline": { "attribOption": ["true", "false"], "type": "boolean" }, "aria-multiselectable": { "attribOption": ["true", "false"], "type": "boolean" }, "aria-orientation": { "attribOption": ["vertical", "horizontal"] }, "aria-owns": { "attribOption": [], "global": "true" }, "aria-posinset": { "attribOption": [] }, "aria-pressed": { "attribOption": ["true", "false", "mixed", "undefined"] }, "aria-readonly": { "attribOption": ["true", "false"] }, "aria-relevant": { "attribOption": ["additions", "removals", "text", "all", "additions text"], "global": "true" }, "aria-required": { "attribOption": ["true", "false"], "type": "boolean" }, "aria-selected": { "attribOption": ["true", "false", "undefined"] }, "aria-setsize": { "attribOption": [] }, "aria-sort": { "attribOption": ["ascending", "descending", "none", "other"] }, "aria-valuemax": { "attribOption": [] }, "aria-valuemin": { "attribOption": [] }, "aria-valuenow": { "attribOption": [] }, "aria-valuetext": { "attribOption": [] }, "accept": { "attribOption": ["text/html", "text/plain", "application/msword", "application/msexcel", "application/postscript", "application/x-zip-compressed", "application/pdf", "application/rtf", "video/x-msvideo", "video/quicktime", "video/x-mpeg2", "audio/x-pn/realaudio", "audio/x-mpeg", "audio/x-waw", "audio/x-aiff", "audio/basic", "image/tiff", "image/jpeg", "image/gif", "image/x-png", "image/x-photo-cd", "image/x-MS-bmp", "image/x-rgb", "image/x-portable-pixmap", "image/x-portable-greymap", "image/x-portablebitmap"] }, "accept-charset": { "attribOption": [] }, "action": { "attribOption": [] }, "align": { "attribOption": [] }, "alt": { "attribOption": [] }, "archive": { "attribOption": [] }, "async": { "attribOption": [], "type": "flag" }, "autocomplete": { "attribOption": ["additional-name", "address-level1", "address-level2", "address-level3", "address-level4", "address-line1", "address-line2", "address-line3", "bday", "bday-year", "bday-day", "bday-month", "billing", "cc-additional-name", "cc-csc", "cc-exp", "cc-exp-month", "cc-exp-year", "cc-family-name", "cc-given-name", "cc-name", "cc-number", "cc-type", "country", "country-name", "current-password", "email", "family-name", "fax", "given-name", "home", "honorific-prefix", "honorific-suffix", "impp", "language", "mobile", "name", "new-password", "nickname", "off", "on", "organization", "organization-title", "pager", "photo", "postal-code", "sex", "shipping", "street-address", "tel-area-code", "tel", "tel-country-code", "tel-extension", "tel-local", "tel-local-prefix", "tel-local-suffix", "tel-national", "transaction-amount", "transaction-currency", "url", "username", "work"] }, "autofocus": { "attribOption": [], "type": "flag" }, "autoplay": { "attribOption": [], "type": "flag" }, "behavior": { "attribOption": ["scroll", "slide", "alternate"] }, "bgcolor": { "attribOption": [], "type": "color" }, "border": { "attribOption": [] }, "challenge": { "attribOption": [] }, "charset": { "attribOption": ["iso-8859-1", "utf-8", "shift_jis", "euc-jp", "big5", "gb2312", "euc-kr", "din_66003-kr", "ns_4551-1-kr", "sen_850200_b", "csISO2022jp", "hz-gb-2312", "ibm852", "ibm866", "irv", "iso-2022-kr", "iso-8859-2", "iso-8859-3", "iso-8859-4", "iso-8859-5", "iso-8859-6", "iso-8859-7", "iso-8859-8", "iso-8859-9", "koi8-r", "ks_c_5601", "windows-1250", "windows-1251", "windows-1252", "windows-1253", "windows-1254", "windows-1255", "windows-1256", "windows-1257", "windows-1258", "windows-874", "x-euc", "asmo-708", "dos-720", "dos-862", "dos-874", "cp866", "cp1256"] }, "checked": { "attribOption": [], "type": "flag" }, "cite": { "attribOption": [] }, "codebase": { "attribOption": [] }, "codetype": { "attribOption": [] }, "cols": { "attribOption": [] }, "colspan": { "attribOption": [] }, "content": { "attribOption": [] }, "controls": { "attribOption": [], "type": "flag" }, "coords": { "attribOption": [] }, "data": { "attribOption": [] }, "datetime": { "attribOption": [] }, "declare": { "attribOption": [], "type": "flag" }, "default": { "attribOption": [], "type": "flag" }, "defer": { "attribOption": [], "type": "flag" }, "direction": { "attribOption": ["left", "right", "up", "down"] }, "dirname": { "attribOption": [] }, "disabled": { "attribOption": [], "type": "flag" }, "enctype": { "attribOption": ["application/x-www-form-urlencoded", "multipart/form-data", "text/plain"] }, "for": { "attribOption": [] }, "form": { "attribOption": [] }, "formaction": { "attribOption": [] }, "formenctype": { "attribOption": ["application/x-www-form-urlencoded", "multipart/form-data", "text/plain"] }, "formmethod": { "attribOption": ["get", "post"] }, "formnovalidate": { "attribOption": [], "type": "flag" }, "formtarget": { "attribOption": ["_blank", "_parent", "_self", "_top"] }, "headers": { "attribOption": [] }, "height": { "attribOption": [] }, "high": { "attribOption": [] }, "href": { "attribOption": [] }, "hreflang": { "attribOption": [] }, "hspace": { "attribOption": [] }, "http-equiv": { "attribOption": ["content-type", "default-style", "refresh"] }, "icon": { "attribOption": [] }, "ismap": { "attribOption": [], "type": "flag" }, "keytype": { "attribOption": ["dsa", "ec", "rsa"] }, "kind": { "attribOption": ["captions", "chapters", "descriptions", "metadata", "subtitles"] }, "label": { "attribOption": [] }, "list": { "attribOption": [] }, "longdesc": { "attribOption": [] }, "loop": { "attribOption": [], "type": "flag" }, "low": { "attribOption": [] }, "manifest": { "attribOption": [] }, "max": { "attribOption": [] }, "maxlength": { "attribOption": [] }, "media": { "attribOption": ["screen", "tty", "tv", "projection", "handheld", "print", "aural", "braille", "embossed", "speech", "all", "width", "min-width", "max-width", "height", "min-height", "max-height", "device-width", "min-device-width", "max-device-width", "device-height", "min-device-height", "max-device-height", "orientation", "aspect-ratio", "min-aspect-ratio", "max-aspect-ratio", "device-aspect-ratio", "min-device-aspect-ratio", "max-device-aspect-ratio", "color", "min-color", "max-color", "color-index", "min-color-index", "max-color-index", "monochrome", "min-monochrome", "max-monochrome", "resolution", "min-resolution", "max-resolution", "scan", "grid"], "allowMultipleValues": "true" }, "mediagroup": { "attribOption": [] }, "method": { "attribOption": ["get", "post"] }, "min": { "attribOption": [] }, "multiple": { "attribOption": [], "type": "flag" }, "muted": { "attribOption": [], "type": "flag" }, "name": { "attribOption": [] }, "meta/name": { "attribOption": ["application-name", "author", "description", "generator", "keywords"] }, "novalidate": { "attribOption": [], "type": "flag" }, "open": { "attribOption": [], "type": "flag" }, "optimum": { "attribOption": [] }, "pattern": { "attribOption": [] }, "placeholder": { "attribOption": [] }, "poster": { "attribOption": [] }, "preload": { "attribOption": ["auto", "metadata", "none"] }, "pubdate": { "attribOption": [] }, "radiogroup": { "attribOption": [] }, "rel": { "attribOption": ["alternate", "author", "bookmark", "help", "license", "next", "nofollow", "noreferrer", "prefetch", "prev", "search", "sidebar", "tag", "external"] }, "link/rel": { "attribOption": ["alternate", "author", "help", "icon", "license", "next", "pingback", "prefetch", "prev", "search", "sidebar", "stylesheet", "tag"] }, "readonly": { "attribOption": [], "type": "flag" }, "required": { "attribOption": [], "type": "flag" }, "reversed": { "attribOption": [], "type": "flag" }, "rows": { "attribOption": [] }, "rowspan": { "attribOption": [] }, "sandbox": { "attribOption": ["allow-forms", "allow-same-origin", "allow-scripts", "allow-top-navigation"] }, "seamless": { "attribOption": [], "type": "flag" }, "selected": { "attribOption": [], "type": "flag" }, "scope": { "attribOption": ["col", "colgroup", "row", "rowgroup"] }, "scoped": { "attribOption": [], "type": "boolean" }, "scrollamount": { "attribOption": [] }, "scrolldelay": { "attribOption": [] }, "shape": { "attribOption": ["circle", "default", "poly","rect"] }, "size": { "attribOption": [] }, "sizes": { "attribOption": ["any"] }, "span": { "attribOption": [] }, "src": { "attribOption": [] }, "srcdoc": { "attribOption": [] }, "srclang": { "attribOption": [] }, "standby": { "attribOption": [] }, "start": { "attribOption": [] }, "step": { "attribOption": [] }, "target": { "attribOption": ["_blank", "_parent", "_self", "_top"] }, "truespeed": { "attribOption": [], "type": "flag" }, "type": { "attribOption": [] }, "button/type": { "attribOption": ["button", "reset", "submit"] }, "command/type": { "attribOption": ["command", "checkbox", "radio"] }, "link/type": { "attribOption": ["text/css"] }, "menu/type": { "attribOption": ["context", "list", "toolbar"] }, "ol/type": { "attribOption": ["1", "a", "A", "i", "I"] }, "script/type": { "attribOption": ["text/javascript", "text/ecmascript", "text/jscript", "text/livescript", "text/tcl", "text/x-javascript", "text/x-ecmascript", "application/x-javascript", "application/x-ecmascript", "application/javascript", "application/ecmascript", "text/babel", "text/jsx"] }, "style/type": { "attribOption": ["text/css"] }, "input/type": { "attribOption": ["button", "checkbox", "color", "date", "datetime", "datetime-local", "email", "file", "hidden", "image", "month", "number", "password", "radio", "range", "reset", "search", "submit", "tel", "text", "time", "url", "week"] }, "usemap": { "attribOption": [] }, "value": { "attribOption": [] }, "vspace": { "attribOption": [] }, "width": { "attribOption": [] }, "wrap": { "attribOption": ["hard", "soft"] }, "xml:lang": { "attribOption": [] }, "xmlns": { "attribOption": [] } } ================================================ FILE: src/extensions/default/HTMLCodeHints/HtmlTags.json ================================================ { "a": { "attributes": ["href", "hreflang", "media", "rel", "target", "type"] }, "abbr": { "attributes": [] }, "address": { "attributes": [] }, "area": { "attributes": ["alt", "coords", "href", "hreflang", "media", "rel", "shape", "target", "type"] }, "article": { "attributes": [] }, "aside": { "attributes": [] }, "audio": { "attributes": ["autoplay", "controls", "loop", "mediagroup", "muted", "preload", "src"] }, "b": { "attributes": [] }, "base": { "attributes": ["href", "target"] }, "bdi": { "attributes": [] }, "bdo": { "attributes": [] }, "big": { "attributes": [] }, "blockquote": { "attributes": ["cite"] }, "body": { "attributes": ["onafterprint", "onbeforeprint", "onbeforeunload", "onhashchange", "onmessage", "onoffline", "ononline", "onpagehide", "onpageshow", "onpopstate", "onredo", "onresize", "onstorage", "onundo", "onunload"] }, "br": { "attributes": [] }, "button": { "attributes": ["autofocus", "disabled", "form", "formaction", "formenctype", "formmethod", "formnovalidate", "formtarget", "name", "type", "value"] }, "canvas": { "attributes": ["height", "width"] }, "caption": { "attributes": [] }, "cite": { "attributes": [] }, "code": { "attributes": [] }, "col": { "attributes": ["span"] }, "colgroup": { "attributes": ["span"] }, "command": { "attributes": ["checked", "disabled", "icon", "label", "radiogroup", "type"] }, "datalist": { "attributes": [] }, "dd": { "attributes": [] }, "del": { "attributes": ["cite", "datetime"] }, "details": { "attributes": ["open"] }, "dfn": { "attributes": [] }, "dialog": { "attributes": ["open"] }, "div": { "attributes": [] }, "dl": { "attributes": [] }, "dt": { "attributes": [] }, "em": { "attributes": [] }, "embed": { "attributes": ["height", "src", "type", "width"] }, "fieldset": { "attributes": ["disabled", "form", "name"] }, "figcaption": { "attributes": [] }, "figure": { "attributes": [] }, "footer": { "attributes": [] }, "form": { "attributes": ["accept-charset", "action", "autocomplete", "enctype", "method", "name", "novalidate", "target"] }, "h1": { "attributes": [] }, "h2": { "attributes": [] }, "h3": { "attributes": [] }, "h4": { "attributes": [] }, "h5": { "attributes": [] }, "h6": { "attributes": [] }, "head": { "attributes": [] }, "header": { "attributes": [] }, "hgroup": { "attributes": [] }, "hr": { "attributes": [] }, "html": { "attributes": ["manifest", "xml:lang", "xmlns"] }, "i": { "attributes": [] }, "iframe": { "attributes": ["height", "name", "sandbox", "seamless", "src", "srcdoc", "width"] }, "ilayer": { "attributes": [] }, "img": { "attributes": ["alt", "height", "ismap", "longdesc", "src", "usemap", "width"] }, "input": { "attributes": ["accept", "alt", "autocomplete", "autofocus", "checked", "dirname", "disabled", "form", "formaction", "formenctype", "formmethod", "formnovalidate", "formtarget", "height", "list", "max", "maxlength", "min", "multiple", "name", "pattern", "placeholder", "readonly", "required", "size", "src", "step", "type", "value", "width"] }, "ins": { "attributes": ["cite", "datetime"] }, "kbd": { "attributes": [] }, "keygen": { "attributes": ["autofocus", "challenge", "disabled", "form", "keytype", "name"] }, "label": { "attributes": ["for", "form"] }, "legend": { "attributes": [] }, "li": { "attributes": ["value"] }, "link": { "attributes": ["disabled", "href", "hreflang", "media", "rel", "sizes", "type"] }, "main": { "attributes": [] }, "map": { "attributes": ["name"] }, "mark": { "attributes": [] }, "marquee": { "attributes": ["align", "behavior", "bgcolor", "direction", "height", "hspace", "loop", "scrollamount", "scrolldelay", "truespeed", "vspace", "width"] }, "menu": { "attributes": ["label", "type"] }, "meta": { "attributes": ["charset", "content", "http-equiv", "name"] }, "meter": { "attributes": ["form", "high", "low", "max", "min", "optimum", "value"] }, "nav": { "attributes": [] }, "noscript": { "attributes": [] }, "object": { "attributes": ["archive", "codebase", "codetype", "data", "declare", "form", "height", "name", "standby", "type", "usemap", "width"] }, "ol": { "attributes": ["reversed", "start", "type"] }, "optgroup": { "attributes": ["disabled", "label"] }, "option": { "attributes": ["disabled", "label", "selected", "value"] }, "output": { "attributes": ["for", "form", "name"] }, "p": { "attributes": [] }, "param": { "attributes": ["name", "value"] }, "pre": { "attributes": [] }, "progress": { "attributes": ["form", "max", "value"] }, "q": { "attributes": ["cite"] }, "rp": { "attributes": [] }, "rt": { "attributes": [] }, "ruby": { "attributes": [] }, "samp": { "attributes": [] }, "script": { "attributes": ["async", "charset", "defer", "src", "type"] }, "section": { "attributes": [] }, "select": { "attributes": ["autofocus", "disabled", "form", "multiple", "name", "required", "size"] }, "small": { "attributes": [] }, "source": { "attributes": ["media", "src", "type"] }, "span": { "attributes": [] }, "strong": { "attributes": [] }, "style": { "attributes": ["disabled", "media", "scoped", "type"] }, "sub": { "attributes": [] }, "summary": { "attributes": [] }, "sup": { "attributes": [] }, "table": { "attributes": ["border"] }, "tbody": { "attributes": [] }, "td": { "attributes": ["colspan", "headers", "rowspan"] }, "template": { "attributes": ["content"] }, "textarea": { "attributes": ["autofocus", "cols", "dirname", "disabled", "form", "label", "maxlength", "name", "placeholder", "readonly", "required", "rows", "wrap"] }, "tfoot": { "attributes": [] }, "th": { "attributes": ["colspan", "headers", "rowspan", "scope"] }, "thead": { "attributes": [] }, "time": { "attributes": ["datetime", "pubdate"] }, "title": { "attributes": [] }, "tr": { "attributes": [] }, "track": { "attributes": ["default", "kind", "label", "src", "srclang"] }, "tt": { "attributes": [] }, "ul": { "attributes": [] }, "var": { "attributes": [] }, "video": { "attributes": ["autoplay", "controls", "height", "loop", "mediagroup", "muted", "poster", "preload", "src", "width"] }, "wbr": { "attributes": [] } } ================================================ FILE: src/extensions/default/HTMLCodeHints/main.js ================================================ /* * Copyright (c) 2012 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ define(function (require, exports, module) { "use strict"; // Load dependent modules var AppInit = brackets.getModule("utils/AppInit"), CodeHintManager = brackets.getModule("editor/CodeHintManager"), HTMLUtils = brackets.getModule("language/HTMLUtils"), PreferencesManager = brackets.getModule("preferences/PreferencesManager"), Strings = brackets.getModule("strings"), HTMLTags = require("text!HtmlTags.json"), HTMLAttributes = require("text!HtmlAttributes.json"), tags, attributes; PreferencesManager.definePreference("codehint.TagHints", "boolean", true, { description: Strings.DESCRIPTION_HTML_TAG_HINTS }); PreferencesManager.definePreference("codehint.AttrHints", "boolean", true, { description: Strings.DESCRIPTION_ATTR_HINTS }); /** * @constructor */ function TagHints() { this.exclusion = null; } /** * Check whether the exclusion is still the same as text after the cursor. * If not, reset it to null. */ TagHints.prototype.updateExclusion = function () { var textAfterCursor; if (this.exclusion && this.tagInfo) { textAfterCursor = this.tagInfo.tagName.substr(this.tagInfo.position.offset); if (!CodeHintManager.hasValidExclusion(this.exclusion, textAfterCursor)) { this.exclusion = null; } } }; /** * Determines whether HTML tag hints are available in the current editor * context. * * @param {Editor} editor * A non-null editor object for the active window. * * @param {string} implicitChar * Either null, if the hinting request was explicit, or a single character * that represents the last insertion and that indicates an implicit * hinting request. * * @return {boolean} * Determines whether the current provider is able to provide hints for * the given editor context and, in case implicitChar is non- null, * whether it is appropriate to do so. */ TagHints.prototype.hasHints = function (editor, implicitChar) { var pos = editor.getCursorPos(); this.tagInfo = HTMLUtils.getTagInfo(editor, pos); this.editor = editor; if (implicitChar === null) { if (this.tagInfo.position.tokenType === HTMLUtils.TAG_NAME) { if (this.tagInfo.position.offset >= 0) { if (this.tagInfo.position.offset === 0) { this.exclusion = this.tagInfo.tagName; } else { this.updateExclusion(); } return true; } } return false; } else { if (implicitChar === "<") { this.exclusion = this.tagInfo.tagName; return true; } return false; } }; /** * Returns a list of availble HTML tag hints if possible for the current * editor context. * * @return {jQuery.Deferred|{ * hints: Array., * match: string, * selectInitial: boolean, * handleWideResults: boolean}} * Null if the provider wishes to end the hinting session. Otherwise, a * response object that provides: * 1. a sorted array hints that consists of strings * 2. a string match that is used by the manager to emphasize matching * substrings when rendering the hint list * 3. a boolean that indicates whether the first result, if one exists, * should be selected by default in the hint list window. * 4. handleWideResults, a boolean (or undefined) that indicates whether * to allow result string to stretch width of display. */ TagHints.prototype.getHints = function (implicitChar) { var query, result; this.tagInfo = HTMLUtils.getTagInfo(this.editor, this.editor.getCursorPos()); if (this.tagInfo.position.tokenType === HTMLUtils.TAG_NAME) { if (this.tagInfo.position.offset >= 0) { this.updateExclusion(); query = this.tagInfo.tagName.slice(0, this.tagInfo.position.offset); result = $.map(tags, function (value, key) { if (key.indexOf(query) === 0) { return key; } }).sort(); return { hints: result, match: query, selectInitial: true, handleWideResults: false }; } } return null; }; /** * Inserts a given HTML tag hint into the current editor context. * * @param {string} hint * The hint to be inserted into the editor context. * * @return {boolean} * Indicates whether the manager should follow hint insertion with an * additional explicit hint request. */ TagHints.prototype.insertHint = function (completion) { var start = {line: -1, ch: -1}, end = {line: -1, ch: -1}, cursor = this.editor.getCursorPos(), charCount = 0; if (this.tagInfo.position.tokenType === HTMLUtils.TAG_NAME) { var textAfterCursor = this.tagInfo.tagName.substr(this.tagInfo.position.offset); if (CodeHintManager.hasValidExclusion(this.exclusion, textAfterCursor)) { charCount = this.tagInfo.position.offset; } else { charCount = this.tagInfo.tagName.length; } } end.line = start.line = cursor.line; start.ch = cursor.ch - this.tagInfo.position.offset; end.ch = start.ch + charCount; if (this.exclusion || completion !== this.tagInfo.tagName) { if (start.ch !== end.ch) { this.editor.document.replaceRange(completion, start, end); } else { this.editor.document.replaceRange(completion, start); } this.exclusion = null; } return false; }; /** * @constructor */ function AttrHints() { this.globalAttributes = this.readGlobalAttrHints(); this.cachedHints = null; this.exclusion = ""; } /** * @private * Parse the code hints from JSON data and extract all hints from property names. * @return {!Array.} An array of code hints read from the JSON data source. */ AttrHints.prototype.readGlobalAttrHints = function () { return $.map(attributes, function (value, key) { if (value.global === "true") { return key; } }); }; /** * Helper function that determines the possible value hints for a given html tag/attribute name pair * * @param {{queryStr: string}} query * The current query * * @param {string} tagName * HTML tag name * * @param {string} attrName * HTML attribute name * * @return {!Array.|$.Deferred} * The (possibly deferred) hints. */ AttrHints.prototype._getValueHintsForAttr = function (query, tagName, attrName) { // We look up attribute values with tagName plus a slash and attrName first. // If the lookup fails, then we fall back to look up with attrName only. Most // of the attributes in JSON are using attribute name only as their properties, // but in some cases like "type" attribute, we have different properties like // "script/type", "link/type" and "button/type". var hints = []; var tagPlusAttr = tagName + "/" + attrName, attrInfo = attributes[tagPlusAttr] || attributes[attrName]; if (attrInfo) { if (attrInfo.type === "boolean") { hints = ["false", "true"]; } else if (attrInfo.attribOption) { hints = attrInfo.attribOption; } } return hints; }; /** * Check whether the exclusion is still the same as text after the cursor. * If not, reset it to null. * * @param {boolean} attrNameOnly * true to indicate that we update the exclusion only if the cursor is inside an attribute name context. * Otherwise, we also update exclusion for attribute value context. */ AttrHints.prototype.updateExclusion = function (attrNameOnly) { if (this.exclusion && this.tagInfo) { var tokenType = this.tagInfo.position.tokenType, offset = this.tagInfo.position.offset, textAfterCursor; if (tokenType === HTMLUtils.ATTR_NAME) { textAfterCursor = this.tagInfo.attr.name.substr(offset); } else if (!attrNameOnly && tokenType === HTMLUtils.ATTR_VALUE) { textAfterCursor = this.tagInfo.attr.value.substr(offset); } if (!CodeHintManager.hasValidExclusion(this.exclusion, textAfterCursor)) { this.exclusion = null; } } }; /** * Determines whether HTML attribute hints are available in the current * editor context. * * @param {Editor} editor * A non-null editor object for the active window. * * @param {string} implicitChar * Either null, if the hinting request was explicit, or a single character * that represents the last insertion and that indicates an implicit * hinting request. * * @return {boolean} * Determines whether the current provider is able to provide hints for * the given editor context and, in case implicitChar is non-null, * whether it is appropriate to do so. */ AttrHints.prototype.hasHints = function (editor, implicitChar) { var pos = editor.getCursorPos(), tokenType, offset, query; this.editor = editor; this.tagInfo = HTMLUtils.getTagInfo(editor, pos); tokenType = this.tagInfo.position.tokenType; offset = this.tagInfo.position.offset; if (implicitChar === null) { query = null; if (tokenType === HTMLUtils.ATTR_NAME) { if (offset >= 0) { query = this.tagInfo.attr.name.slice(0, offset); } } else if (tokenType === HTMLUtils.ATTR_VALUE) { if (this.tagInfo.position.offset >= 0) { query = this.tagInfo.attr.value.slice(0, offset); } else { // We get negative offset for a quoted attribute value with some leading whitespaces // as in = 0) { if (tokenType === HTMLUtils.ATTR_NAME && offset === 0) { this.exclusion = this.tagInfo.attr.name; } else { this.updateExclusion(false); } } return query !== null; } else { if (implicitChar === " " || implicitChar === "'" || implicitChar === "\"" || implicitChar === "=") { if (tokenType === HTMLUtils.ATTR_NAME) { this.exclusion = this.tagInfo.attr.name; } return true; } return false; } }; /** * Returns a list of availble HTML attribute hints if possible for the * current editor context. * * @return {jQuery.Deferred|{ * hints: Array., * match: string, * selectInitial: boolean, * handleWideResults: boolean}} * Null if the provider wishes to end the hinting session. Otherwise, a * response object that provides: * 1. a sorted array hints that consists of strings * 2. a string match that is used by the manager to emphasize matching * substrings when rendering the hint list * 3. a boolean that indicates whether the first result, if one exists, * should be selected by default in the hint list window. * 4. handleWideResults, a boolean (or undefined) that indicates whether * to allow result string to stretch width of display. */ AttrHints.prototype.getHints = function (implicitChar) { var cursor = this.editor.getCursorPos(), query = {queryStr: null}, tokenType, offset, result = []; this.tagInfo = HTMLUtils.getTagInfo(this.editor, cursor); tokenType = this.tagInfo.position.tokenType; offset = this.tagInfo.position.offset; if (tokenType === HTMLUtils.ATTR_NAME || tokenType === HTMLUtils.ATTR_VALUE) { query.tag = this.tagInfo.tagName; if (offset >= 0) { if (tokenType === HTMLUtils.ATTR_NAME) { query.queryStr = this.tagInfo.attr.name.slice(0, offset); } else { query.queryStr = this.tagInfo.attr.value.slice(0, offset); query.attrName = this.tagInfo.attr.name; } this.updateExclusion(false); } else if (tokenType === HTMLUtils.ATTR_VALUE) { // We get negative offset for a quoted attribute value with some leading whitespaces // as in \n" + "\n" + "\n" + "

      Heading

      \n" + // tag without whitespace "

      Subheading

      \n" + // tag with whitespace "

      \n" + // tag without attributes "
      \n" + // tag with two attributes "
      \n" + "\n"; var testDocument, testEditor; beforeEach(function () { // create dummy Document for the Editor testDocument = SpecRunnerUtils.createMockDocument(defaultContent, "html"); // create Editor instance (containing a CodeMirror instance) $("body").append("
      "); testEditor = new Editor(testDocument, true, $("#editor").get(0)); }); afterEach(function () { testEditor.destroy(); testEditor = null; $("#editor").remove(); testDocument = null; }); // Ask provider for hints at current cursor position; expect it to return some function expectHints(provider) { expect(provider.hasHints(testEditor, null)).toBe(true); var hintsObj = provider.getHints(); expect(hintsObj).toBeTruthy(); return hintsObj.hints; // return just the array of hints } // Ask provider for hints at current cursor position; expect it NOT to return any function expectNoHints(provider) { expect(provider.hasHints(testEditor, null)).toBe(false); } // Expect hintList to contain tag names, starting with given value (if unspecified, expects the default unfiltered list) function verifyTagHints(hintList, expectedFirstHint) { expect(hintList.indexOf("id")).toBe(-1); // make sure attribute names aren't sneaking in there expectedFirstHint = expectedFirstHint || "a"; // assume unfiltered lists always start with "a" expect(hintList[0]).toBe(expectedFirstHint); } // Expect hintList to contain attribute names, starting with given value (if unspecified, expects the default unfilered list) function verifyAttrHints(hintList, expectedFirstHint) { expect(hintList.indexOf("div")).toBe(-1); // make sure tag names aren't sneaking in there expectedFirstHint = expectedFirstHint || "accesskey"; // assume unfiltered lists always start with "accesskey" expect(hintList[0]).toBe(expectedFirstHint); } describe("Tag hint provider", function () { it("should not hint within \n" + "\n" + "\n" + "\n" + "\n"); testEditor.setCursorPos({ line: 3, ch: 9 }); // cursor after the > in " some other stuff
      ================================================ FILE: src/extensions/default/JavaScriptCodeHints/unittest-files/basic-test-files/test.html ================================================ ================================================ FILE: src/extensions/default/JavaScriptCodeHints/unittest-files/module-test-files/Car.js ================================================ /*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, maxerr: 50 */ /*global define, $ */ define(function (require, exports, module) { "use strict"; exports.name = "Honda"; exports.model = "2013 MDX"; }); ================================================ FILE: src/extensions/default/JavaScriptCodeHints/unittest-files/module-test-files/china/Cup.js ================================================ /*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, maxerr: 50 */ /*global define, $ */ define(function (require, exports, module) { "use strict"; function Cup() { this.empty = true; } Cup.prototype.fill = function () { this.empty = false; }; Cup.prototype.emptyIt = function () { }; Cup.prototype.full = function () { }; Cup.prototype.empty = function () { }; exports.Cup = Cup; }); ================================================ FILE: src/extensions/default/JavaScriptCodeHints/unittest-files/module-test-files/china/cupFiller.js ================================================ /*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, maxerr: 50 */ /*global define, $ */ define(function (require, exports, module) { "use strict"; var Cup = require('china/Cup').Cup; var coffeeCup = new Cup(false); coffeeCup. }); ================================================ FILE: src/extensions/default/JavaScriptCodeHints/unittest-files/module-test-files/credits.js ================================================ /*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, maxerr: 50 */ /*global define, $ */ define(function () { 'use strict'; return { getCredits: function () { var credits = "100"; return credits; } }; }); ================================================ FILE: src/extensions/default/JavaScriptCodeHints/unittest-files/module-test-files/module_tests.js ================================================ /*jslint vars: true, plusplus: true, devel: true, browser: true, node: true, nomen: true, indent: 4, maxerr: 50 */ /*global brackets, define, require, $ */ define(function (require, exports, module) { 'use strict'; var moduleName = (function () { // this becomes public due to the reference exposure in the return below var addMessage = function (test) { this.publicMethod1(); // a test here to check the method showing }; var name = "new name"; // this is the "revealed" part of the module return { addMessage: addMessage, name: name }; }()); moduleName.publicMethod1 = function () { // test for methods:name, addMessage, publicMethod1, priv }; // extending a new module var extendedModule = (function (oldModule) { var parent = oldModule; parent.privilegedMethod = function () { }; var privateMethod2 = function () { // test parent. make sure all methods defined in moduleName showing up here }; return { newMethod : function () { // should see methods for oldModule oldModule., parent. // should see privateMethod2( } }; }(moduleName || {}));//Module object is the existing module to extend. // test here: check extendedModule moduleName.addMessage("test"); moduleName.name = " new name"; //parent module var SearchEngine = (function () { //Private Method. var luckyAlgo = function () { //create one random number. return Math.floor(Math.random() * 11); }; //Returning the object return { //privileged method. getYourLuckyNumber : function () { //Has access to its private method because of closure. return luckyAlgo(); } }; }());//Self executing method. // test SearchEngine.getYourLuckyNumber().toExponential(); SearchEngine.subSearch = (function () { //Private variable. var defaultColor = "Orange"; //private method. var myColorAlgo = function (num) { switch (num) { case 1: defaultColor = "Green"; break; } }; return { getYourLuckyColor : function () { //access to private variable because of closure. myColorAlgo(SearchEngine.getYourLuckyNumber()); return defaultColor; } }; }()); require(["shirt"], function (myShirt) { // myShirt. test here }); var t2 = require("shirt"); var size = t2.size; var color = t2.color; t2.material(); var purchaseModule = require('purchase'); purchaseModule.purchaseProduct(); var hondaCar = require('Car'); }); ================================================ FILE: src/extensions/default/JavaScriptCodeHints/unittest-files/module-test-files/products.js ================================================ /*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, maxerr: 50 */ /*global define, $ */ define(function (products) { 'use strict'; return { reserveProduct: function () { console.log("Function : reserveProduct"); return true; } }; }); ================================================ FILE: src/extensions/default/JavaScriptCodeHints/unittest-files/module-test-files/purchase.js ================================================ /*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, maxerr: 50 */ /*global define, $ */ define(["credits", "products"], function (credits, products) { 'use strict'; return { purchaseProduct: function () { var credit = credits.getCredits(); if (credit > 0) { products.reserveProduct(); return true; } return false; } }; }); ================================================ FILE: src/extensions/default/JavaScriptCodeHints/unittest-files/module-test-files/shirt.js ================================================ /*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, maxerr: 50 */ /*global define, $ */ define(function () { 'use strict'; return { color: "black", size: 10, material: function () { return "cotton"; } }; }); ================================================ FILE: src/extensions/default/JavaScriptCodeHints/unittest-files/non-module-test-files/.jscodehints ================================================ { "excluded-directories" : ["/ex[\\w]*ed/"], "excluded-files" : ["require.js", "jquery*.js", "less*.min.js", "ember*.js", "d2?.js", "d3*"], "max-file-count": 100, "max-file-size": 524288 } ================================================ FILE: src/extensions/default/JavaScriptCodeHints/unittest-files/non-module-test-files/a/a.js ================================================ var app = app || {}; (function () { 'use strict'; app.a = 1; }()); ================================================ FILE: src/extensions/default/JavaScriptCodeHints/unittest-files/non-module-test-files/app.js ================================================ /*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, maxerr: 50 */ /*global */ var app = app || {}; (function () { 'use strict'; app.d = 8; }()); ================================================ FILE: src/extensions/default/JavaScriptCodeHints/unittest-files/non-module-test-files/b/b.js ================================================ /*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, maxerr: 50 */ /*global */ var app = app || {}; (function () { 'use strict'; app.b = 2; }()); ================================================ FILE: src/extensions/default/JavaScriptCodeHints/unittest-files/non-module-test-files/b/b1/b1.js ================================================ /*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, maxerr: 50 */ /*global */ var app = app || {}; (function () { 'use strict'; app.b1 = 21; }()); ================================================ FILE: src/extensions/default/JavaScriptCodeHints/unittest-files/non-module-test-files/c/c.js ================================================ /*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, maxerr: 50 */ /*global */ var app = app || {}; (function () { 'use strict'; app.c = 3; }()); ================================================ FILE: src/extensions/default/JavaScriptCodeHints/unittest-files/non-module-test-files/d/d.js ================================================ /*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, maxerr: 50 */ /*global */ var app = app || {}; (function () { 'use strict'; app.d = 4; }()); ================================================ FILE: src/extensions/default/JavaScriptCodeHints/unittest-files/non-module-test-files/d/d2.js ================================================ /*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, maxerr: 50 */ /*global */ var app = app || {}; (function () { 'use strict'; app.d2 = 42; }()); ================================================ FILE: src/extensions/default/JavaScriptCodeHints/unittest-files/non-module-test-files/d/d23.js ================================================ /*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, maxerr: 50 */ /*global */ var app = app || {}; (function () { 'use strict'; app.d23 = 423; }()); ================================================ FILE: src/extensions/default/JavaScriptCodeHints/unittest-files/non-module-test-files/d/d3-excluded.js ================================================ /*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, maxerr: 50 */ /*global */ var app = app || {}; (function () { 'use strict'; app.d3Excluded = 43; }()); ================================================ FILE: src/extensions/default/JavaScriptCodeHints/unittest-files/non-module-test-files/excluded/e.js ================================================ /*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, maxerr: 50 */ /*global */ var app = app || {}; (function () { 'use strict'; app.e = 5; }()); ================================================ FILE: src/extensions/default/JavaScriptCodeHints/unittest-files/preference-test-files/defaults-test/.jscodehints ================================================ { } ================================================ FILE: src/extensions/default/JavaScriptCodeHints/unittest-files/preference-test-files/negative-test/.jscodehints ================================================ { "excluded-directories" : [], "excluded-files" : [], "max-file-count": -100, "max-file-size": 0 } ================================================ FILE: src/extensions/default/JavaScriptCodeHints/unittest-files/preference-test-files/positive-test/.jscodehints ================================================ { "excluded-directories" : ["excluded-dir1", "/^excluded-dir2-[\\d]$/"], "excluded-files" : ["file1?.js", "file2*.js", "file3.js", "/file4[x|y|z]?.js/"], "max-file-count": 512, "max-file-size": 100000 } ================================================ FILE: src/extensions/default/JavaScriptCodeHints/unittests.js ================================================ /* * Copyright (c) 2013 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /*jslint regexp: true */ /*global describe, it, xit, expect, beforeEach, afterEach, waitsFor, runs, waitsForDone, waitsForFail, beforeFirst, afterLast */ define(function (require, exports, module) { "use strict"; var Commands = brackets.getModule("command/Commands"), CommandManager = brackets.getModule("command/CommandManager"), MainViewManager = brackets.getModule("view/MainViewManager"), DocumentManager = brackets.getModule("document/DocumentManager"), EditorManager = brackets.getModule("editor/EditorManager"), FileSystem = brackets.getModule("filesystem/FileSystem"), FileUtils = brackets.getModule("file/FileUtils"), PreferencesManager = brackets.getModule("preferences/PreferencesManager"), SpecRunnerUtils = brackets.getModule("spec/SpecRunnerUtils"), JSCodeHints = require("main"), Preferences = brackets.getModule("JSUtils/Preferences"), ScopeManager = brackets.getModule("JSUtils/ScopeManager"), HintUtils = brackets.getModule("JSUtils/HintUtils"), HintUtils2 = require("HintUtils2"), ParameterHintProvider = require("ParameterHintsProvider").JSParameterHintsProvider, phProvider = new ParameterHintProvider(); var extensionPath = FileUtils.getNativeModuleDirectoryPath(module), testPath = extensionPath + "/unittest-files/basic-test-files/file1.js", testHtmlPath = extensionPath + "/unittest-files/basic-test-files/index.html", testDoc = null, testEditor, preTestText; CommandManager.register("test-file-open", Commands.FILE_OPEN, function (fileInfo) { // Register a command for FILE_OPEN, which the jump to def code will call return DocumentManager.getDocumentForPath(fileInfo.fullPath).done(function (doc) { MainViewManager._edit(MainViewManager.ACTIVE_PANE, doc); }); }); describe("JavaScript Code Hinting", function () { // Helper function for testing cursor position function fixPos(pos) { if (!("sticky" in pos)) { pos.sticky = null; } return pos; } /* * Ask provider for hints at current cursor position; expect it to * return some * * @param {Object} provider - a CodeHintProvider object * @param {string} key - the charCode of a key press that triggers the * CodeHint provider * @return {boolean} - whether the provider has hints in the context of * the test editor */ function expectHints(provider, key) { if (key === undefined) { key = null; } expect(provider.hasHints(testEditor, key)).toBe(true); return provider.getHints(null); } /* * Ask provider for hints at current cursor position; expect it NOT to * return any * * @param {Object} provider - a CodeHintProvider object * @param {string} key - the charCode of a key press that triggers the * CodeHint provider */ function expectNoHints(provider, key) { if (key === undefined) { key = null; } expect(provider.hasHints(testEditor, key)).toBe(false); } /* * Return the index at which hint occurs in hintList * * @param {Array.} hintList - the list of hints * @param {string} hint - the hint to search for * @return {number} - the index into hintList at which the hint occurs, * or -1 if it does not */ function _indexOf(hintList, hint) { var index = -1, counter = 0; for (counter; counter < hintList.length; counter++) { if (hintList[counter].data("token").value === hint) { index = counter; break; } } return index; } /* * Wait for a hint response object to resolve, then apply a callback * to the result * * @param {Object + jQuery.Deferred} hintObj - a hint response object, * possibly deferred * @param {Function} callback - the callback to apply to the resolved * hint response object */ function _waitForHints(hintObj, callback) { var complete = false, hintList = null; if (hintObj.hasOwnProperty("hints")) { complete = true; hintList = hintObj.hints; } else { hintObj.done(function (obj) { complete = true; hintList = obj.hints; }); } waitsFor(function () { return complete; }, "Expected hints did not resolve", 3000); runs(function () { callback(hintList); }); } /* * Test if hints should be closed or not closed at a given position. * * @param {Object} provider - a CodeHintProvider object * @param {Object + jQuery.Deferred} hintObj - a hint response object, * possibly deferred * @param {line: number, ch: number} newPos - new position to move to * after hints are received. * @param {boolean} expectedValue - true if hints should close, * false otherwise. */ function expectCloseHints(provider, hintObj, newPos, expectedValue) { _waitForHints(hintObj, function (hintList) { testEditor.setCursorPos(newPos); expect(provider.shouldCloseHints(JSCodeHints.getSession())).toBe(expectedValue); }); } /* * Expect a given list of hints to be absent from a given hint * response object * * @param {Object + jQuery.Deferred} hintObj - a hint response object, * possibly deferred * @param {Array.} absentHints - a list of hints that should not * be present in the hint response */ function hintsAbsent(hintObj, absentHints) { _waitForHints(hintObj, function (hintList) { expect(hintList).toBeTruthy(); absentHints.forEach(function (absentHint) { expect(_indexOf(hintList, absentHint)).toBe(-1); }); }); } /* * Expect a given list of hints to be present in a given hint * response object * * @param {Object + jQuery.Deferred} hintObj - a hint response object, * possibly deferred * @param {Array.} expectedHints - a list of hints that should be * present in the hint response */ function hintsPresent(hintObj, expectedHints) { _waitForHints(hintObj, function (hintList) { expect(hintList).toBeTruthy(); expectedHints.forEach(function (expectedHint) { expect(_indexOf(hintList, expectedHint)).not.toBe(-1); }); }); } /* * Expect a given list of hints to be present in the given order in a * given hint response object * * @param {Object + jQuery.Deferred} hintObj - a hint response object, * possibly deferred * @param {Array.} expectedHints - a list of hints that should be * present in the given order in the hint response */ function hintsPresentOrdered(hintObj, expectedHints) { var prevIndex = -1, currIndex; _waitForHints(hintObj, function (hintList) { expect(hintList).toBeTruthy(); expectedHints.forEach(function (expectedHint) { currIndex = _indexOf(hintList, expectedHint); expect(currIndex).toBeGreaterThan(prevIndex); prevIndex = currIndex; }); }); } /* * Expect a given list of hints to be present in a given hint * response object, and no more. * * @param {Object + jQuery.Deferred} hintObj - a hint response object, * possibly deferred * @param {Array.} expectedHints - a list of hints that should be * present in the hint response, and no more. */ function hintsPresentExact(hintObj, expectedHints) { _waitForHints(hintObj, function (hintList) { expect(hintList).toBeTruthy(); expect(hintList.length).toBe(expectedHints.length); expectedHints.forEach(function (expectedHint, index) { expect(hintList[index].data("token").value).toBe(expectedHint); }); }); } /** * Find the index of a string in a list of hints. * @param {Array} hintList - the list of hints * @param {string} hintSelection - the string represenation of the hint * to find the index of * @return {number} the index of the hint corresponding to the hintSelection */ function findHint(hintList, hintSelection) { var i, l; for (i = 0, l = hintList.length; i < l; ++i) { var current = hintList[i].data("token"); if (hintSelection === current.value) { return i; } } return -1; } /* * Simulation of selection of a particular hint in a hint list. * Presumably results in side effects in the hint provider's * current editor context. * * @param {Object} provider - a CodeHint provider object * @param {Object} hintObj - a hint response object from that provider, * possibly deferred * @param {string} hintSelection - the hint to select */ function selectHint(provider, hintObj, hintSelection) { expectHints(provider); _waitForHints(hintObj, function (hintList) { expect(hintList).toBeTruthy(); var index = findHint(hintList, hintSelection); expect(hintList[index].data("token")).toBeTruthy(); expect(provider.insertHint(hintList[index])).toBe(false); }); } /** * Wait for the editor to change positions, such as after a jump to * definition has been triggered. Will timeout after 3 seconds * * @param {{line:number, ch:number}} oldLocation - the original line/col * @param {Function} callback - the callback to apply once the editor has changed position */ function _waitForJump(jumpPromise, callback) { var cursor = null, complete = false; jumpPromise.done(function () { complete = true; }); waitsFor(function () { var activeEditor = EditorManager.getActiveEditor(); cursor = activeEditor.getCursorPos(); return complete; }, "Expected jump did not occur", 3000); runs(function () { callback(cursor); }); } /** * Trigger a jump to definition, and verify that the editor jumped to * the expected location. The new location is the variable definition * or function definition of the variable or function at the current * cursor location. Jumping to the new location will cause a new editor * to be opened or open an existing editor. * * @param {{line:number, ch:number, file:string}} expectedLocation - the * line, column, and optionally the new file the editor should jump to. If the * editor is expected to stay in the same file, then file may be omitted. */ function editorJumped(expectedLocation) { var jumpPromise = JSCodeHints.handleJumpToDefinition(); _waitForJump(jumpPromise, function (newCursor) { expect(newCursor.line).toBe(expectedLocation.line); expect(newCursor.ch).toBe(expectedLocation.ch); if (expectedLocation.file) { var activeEditor = EditorManager.getActiveEditor(); expect(activeEditor.document.file.name).toBe(expectedLocation.file); } }); } /** * Verify there is no parameter hint at the current cursor. */ function expectNoParameterHint() { var requestStatus = undefined; runs(function () { var request = phProvider._getParameterHint(); request.fail(function (status) { requestStatus = status; }); waitsForFail(request, "ParameterHints"); }); runs(function () { expect(requestStatus).toBe(null); }); } /** * Verify the parameter hint is not visible. */ function expectParameterHintClosed() { expect(phProvider.isHintDisplayed()).toBe(false); } /** * Show a function hint based on the code at the cursor. Verify the * hint matches the passed in value. * * @param {Array<{name: string, type: string, isOptional: boolean}>} * expectedParams - array of records, where each element of the array * describes a function parameter. If null, then no hint is expected. * @param {number} expectedParameter - the parameter at cursor. */ function expectParameterHint(expectedParams, expectedParameter) { var requestHints = undefined, request = null; function expectHint(hint) { var params = hint.parameters, n = params.length, i; // compare params to expected params expect(params.length).toBe(expectedParams.length); expect(hint.currentIndex).toBe(expectedParameter); for (i = 0; i < n; i++) { expect(params[i].name).toBe(expectedParams[i].name); expect(params[i].type).toBe(expectedParams[i].type); if (params[i].isOptional) { expect(expectedParams[i].isOptional).toBeTruthy(); } else { expect(expectedParams[i].isOptional).toBeFalsy(); } } } runs(function () { request = phProvider._getParameterHint(); if (expectedParams === null) { request.fail(function (result) { requestHints = result; }); waitsForFail(request, "ParameterHints"); } else { request.done(function (result) { requestHints = result; }); waitsForDone(request, "ParameterHints"); } }); if (expectedParams === null) { expect(requestHints).toBe(null); } else { expectHint(requestHints); } } function setupTest(path, primePump) { // FIXME: primePump argument ignored even though used below DocumentManager.getDocumentForPath(path).done(function (doc) { testDoc = doc; }); waitsFor(function () { return testDoc !== null; }, "Unable to open test document", 10000); // create Editor instance (containing a CodeMirror instance) runs(function () { testEditor = SpecRunnerUtils.createMockEditorForDocument(testDoc); preTestText = testDoc.getText(); waitsForDone(ScopeManager._readyPromise()); waitsForDone(ScopeManager._maybeReset(JSCodeHints.getSession(), testDoc, true)); }); } function tearDownTest() { // Restore the pre-test version of the text here because the hinter // will update the contents of the previous document in tern. testDoc.setText(preTestText); // The following call ensures that the document is reloaded // from disk before each test MainViewManager._closeAll(MainViewManager.ALL_PANES); SpecRunnerUtils.destroyMockEditor(testDoc); testEditor = null; testDoc = null; } describe("JavaScript Code Hinting Basic", function () { beforeFirst(function () { brackets._configureJSCodeHints({ noReset: true }); }); afterLast(function () { brackets._configureJSCodeHints({ noReset: false }); }); beforeEach(function () { setupTest(testPath, false); }); afterEach(function () { tearDownTest(); }); it("should not list hints in string literal", function () { testEditor.setCursorPos({ line: 20, ch: 22 }); expectNoHints(JSCodeHints.jsHintProvider); }); it("should list declared variable and function names in outer scope", function () { testEditor.setCursorPos({ line: 6, ch: 0 }); var hintObj = expectHints(JSCodeHints.jsHintProvider); hintsPresent(hintObj, ["A2", "A3", "funB", "A1"]); }); it("should filter hints by query", function () { testEditor.setCursorPos({ line: 5, ch: 10 }); var hintObj = expectHints(JSCodeHints.jsHintProvider); hintsPresent(hintObj, ["A1", "A2", "A3"]); hintsAbsent(hintObj, ["funB"]); }); it("should list keywords", function () { testEditor.setCursorPos({ line: 6, ch: 0 }); var hintObj = expectHints(JSCodeHints.jsHintProvider); hintsPresent(hintObj, ["break", "case", "catch"]); }); xit("should list explicitly defined globals from JSLint annotations", function () { testEditor.setCursorPos({ line: 6, ch: 0 }); var hintObj = expectHints(JSCodeHints.jsHintProvider); hintsPresent(hintObj, ["brackets", "$"]); }); xit("should list implicitly defined globals from JSLint annotations", function () { testEditor.setCursorPos({ line: 6, ch: 0 }); var hintObj = expectHints(JSCodeHints.jsHintProvider); hintsPresent(hintObj, ["alert", "console", "confirm", "navigator", "window", "frames"]); }); it("should NOT list implicitly defined globals from missing JSLint annotations", function () { testEditor.setCursorPos({ line: 6, ch: 0 }); var hintObj = expectHints(JSCodeHints.jsHintProvider); hintsAbsent(hintObj, ["ActiveXObject", "CScript", "VBArray"]); }); it("should NOT list explicitly defined globals from JSLint annotations in other files", function () { testEditor.setCursorPos({ line: 6, ch: 0 }); var hintObj = expectHints(JSCodeHints.jsHintProvider); hintsAbsent(hintObj, ["crazyGlobal", "anotherCrazyGlobal"]); }); it("should NOT list implicitly defined globals from JSLint annotations in other files", function () { testEditor.setCursorPos({ line: 6, ch: 0 }); var hintObj = expectHints(JSCodeHints.jsHintProvider); hintsAbsent(hintObj, ["spawn", "version", "toint32"]); }); it("should list literal constants", function () { testEditor.setCursorPos({ line: 6, ch: 0 }); var hintObj = expectHints(JSCodeHints.jsHintProvider); hintsPresent(hintObj, ["null", "undefined", "true", "false"]); }); it("should NOT list variables, function names and parameter names out of scope", function () { testEditor.setCursorPos({ line: 6, ch: 0 }); var hintObj = expectHints(JSCodeHints.jsHintProvider); hintsAbsent(hintObj, ["paramB2", "paramB1"]); }); it("should NOT list variables, function names and parameter names in other files", function () { testEditor.setCursorPos({ line: 6, ch: 0 }); var hintObj = expectHints(JSCodeHints.jsHintProvider); hintsAbsent(hintObj, ["E1", "E2"]); }); it("should NOT list property names on value lookups", function () { testEditor.setCursorPos({ line: 6, ch: 0 }); var hintObj = expectHints(JSCodeHints.jsHintProvider); hintsAbsent(hintObj, ["propA", "propB", "propC"]); }); it("should list declared variable, function and parameter names in inner scope", function () { testEditor.setCursorPos({ line: 12, ch: 0 }); var hintObj = expectHints(JSCodeHints.jsHintProvider); hintsPresent(hintObj, ["B1", "B2", "funC", "paramB1", "paramB2", "funB", "A1", "A2", "A3"]); }); xit("should list string literals that occur in the file", function () { testEditor.setCursorPos({ line: 12, ch: 0 }); var hintObj = expectHints(JSCodeHints.jsHintProvider); hintsPresent(hintObj, ["use strict"]); }); it("should NOT list string literals from other files", function () { testEditor.setCursorPos({ line: 6, ch: 0 }); var hintObj = expectHints(JSCodeHints.jsHintProvider); hintsAbsent(hintObj, ["a very nice string"]); }); it("should list property names that have been declared in the file", function () { testEditor.setCursorPos({ line: 17, ch: 11 }); var hintObj = expectHints(JSCodeHints.jsHintProvider); hintsPresent(hintObj, ["propB"]); }); it("should list identifier names that occur in other files", function () { testEditor.setCursorPos({ line: 16, ch: 0 }); var hintObj = expectHints(JSCodeHints.jsHintProvider); hintsPresent(hintObj, ["D1", "D2"]); }); it("should NOT list variable, parameter or function names on property lookups", function () { testEditor.setCursorPos({ line: 17, ch: 11 }); var hintObj = expectHints(JSCodeHints.jsHintProvider); hintsAbsent(hintObj, ["A1", "A2", "funB", "paramB1", "paramB2", "B1", "B2", "funC", "paramC1", "paramC2"]); }); it("should NOT list keywords on property lookups", function () { testEditor.setCursorPos({ line: 17, ch: 11 }); var hintObj = expectHints(JSCodeHints.jsHintProvider); hintsAbsent(hintObj, ["case", "function", "var"]); }); it("should close hints when move over '.' ", function () { testEditor.setCursorPos({ line: 17, ch: 11 }); var hintObj = expectHints(JSCodeHints.jsHintProvider); expectCloseHints(JSCodeHints.jsHintProvider, hintObj, { line: 17, ch: 10 }, true); }); it("should close hints only when move off the end of a property ", function () { testEditor.setCursorPos({ line: 17, ch: 11 }); var hintObj = expectHints(JSCodeHints.jsHintProvider); expectCloseHints(JSCodeHints.jsHintProvider, hintObj, { line: 17, ch: 12 }, false); expectCloseHints(JSCodeHints.jsHintProvider, hintObj, { line: 17, ch: 13 }, false); expectCloseHints(JSCodeHints.jsHintProvider, hintObj, { line: 17, ch: 14 }, false); expectCloseHints(JSCodeHints.jsHintProvider, hintObj, { line: 17, ch: 15 }, false); expectCloseHints(JSCodeHints.jsHintProvider, hintObj, { line: 17, ch: 16 }, false); expectCloseHints(JSCodeHints.jsHintProvider, hintObj, { line: 17, ch: 17 }, true); }); it("should close hints only when move off the beginning of an identifier ", function () { testEditor.setCursorPos({ line: 17, ch: 10 }); var hintObj = expectHints(JSCodeHints.jsHintProvider); expectCloseHints(JSCodeHints.jsHintProvider, hintObj, { line: 17, ch: 9 }, false); expectCloseHints(JSCodeHints.jsHintProvider, hintObj, { line: 17, ch: 8 }, false); expectCloseHints(JSCodeHints.jsHintProvider, hintObj, { line: 17, ch: 7 }, true); }); it("should close hints only when move off the beginning of a keyword ", function () { testEditor.setCursorPos({ line: 24, ch: 7 }); var hintObj = expectHints(JSCodeHints.jsHintProvider); hintsPresent(hintObj, ["var"]); expectCloseHints(JSCodeHints.jsHintProvider, hintObj, { line: 24, ch: 6 }, false); expectCloseHints(JSCodeHints.jsHintProvider, hintObj, { line: 24, ch: 5 }, false); expectCloseHints(JSCodeHints.jsHintProvider, hintObj, { line: 24, ch: 4 }, false); expectCloseHints(JSCodeHints.jsHintProvider, hintObj, { line: 24, ch: 3 }, true); }); it("should NOT list implicit hints on left-brace", function () { testEditor.setCursorPos({ line: 6, ch: 0 }); expectNoHints(JSCodeHints.jsHintProvider, "{"); }); it("should list explicit hints for variable and function names", function () { testEditor.setCursorPos({ line: 6, ch: 0 }); var hintObj = expectHints(JSCodeHints.jsHintProvider, null); hintsPresent(hintObj, ["A2", "A3", "funB", "A1"]); }); it("should list implicit hints when typing property lookups", function () { testEditor.setCursorPos({ line: 17, ch: 10 }); var hintObj = expectHints(JSCodeHints.jsHintProvider, "."); hintsPresent(hintObj, ["B1", "paramB1"]); }); it("should give priority to identifier names associated with the current context", function () { testEditor.setCursorPos({ line: 16, ch: 0 }); var hintObj = expectHints(JSCodeHints.jsHintProvider); hintsPresentOrdered(hintObj, ["C1", "B1"]); hintsPresentOrdered(hintObj, ["C2", "B2"]); }); it("should give priority to property names associated with the current context from other files", function () { testEditor.setCursorPos({ line: 16, ch: 0 }); var hintObj = expectHints(JSCodeHints.jsHintProvider); hintsPresentOrdered(hintObj, ["C1", "D1"]); hintsPresentOrdered(hintObj, ["B1", "D1"]); hintsPresentOrdered(hintObj, ["A1", "D1"]); hintsPresentOrdered(hintObj, ["funB", "funE"]); }); xit("should choose the correct delimiter for string literal hints with no query", function () { var start = { line: 18, ch: 0 }, end = { line: 18, ch: 18 }; testEditor.setCursorPos(start); var hintObj = expectHints(JSCodeHints.jsHintProvider); selectHint(JSCodeHints.jsHintProvider, hintObj, 13); // hint 13 is "hello\\\"world!" runs(function () { expect(testEditor.getCursorPos()).toEqual(end); expect(testDoc.getRange(start, end)).toEqual('"hello\\\\\\" world!"'); }); }); it("should insert value hints with no current query", function () { var start = { line: 6, ch: 0 }, end = { line: 6, ch: 2 }; testEditor.setCursorPos(start); var hintObj = expectHints(JSCodeHints.jsHintProvider); selectHint(JSCodeHints.jsHintProvider, hintObj, "A2"); runs(function () { //expect(testEditor.getCursorPos()).toEqual(end); expect(testDoc.getRange(start, end)).toEqual("A2"); }); }); it("should insert value hints replacing the current query", function () { var start = { line: 5, ch: 10 }, // A3 = A2; before = { line: 5, ch: 9 }, end = { line: 5, ch: 11 }; testEditor.setCursorPos(start); var hintObj = expectHints(JSCodeHints.jsHintProvider); hintsPresent(hintObj, ["A1", "A2", "A3"]); selectHint(JSCodeHints.jsHintProvider, hintObj, "A1"); runs(function () { //expect(testEditor.getCursorPos()).toEqual(end); expect(testDoc.getRange(before, end)).toEqual("A1"); }); }); it("should insert property hints with no current query", function () { var start = { line: 6, ch: 0 }, middle = { line: 6, ch: 3 }, end = { line: 6, ch: 8 }; testDoc.replaceRange("A1.", start, start); testEditor.setCursorPos(middle); var hintObj = expectHints(JSCodeHints.jsHintProvider); selectHint(JSCodeHints.jsHintProvider, hintObj, "propA"); runs(function () { expect(fixPos(testEditor.getCursorPos())).toEqual(fixPos(end)); expect(testDoc.getRange(start, end)).toEqual("A1.propA"); expect(testDoc.getLine(end.line).length).toEqual(8); }); }); it("should insert, not replace, property hints with no current query", function () { var start = { line: 6, ch: 0 }, middle = { line: 6, ch: 3 }, end = { line: 6, ch: 8 }, endplus = { line: 6, ch: 12 }; testDoc.replaceRange("A1.prop", start, start); testEditor.setCursorPos(middle); var hintObj = expectHints(JSCodeHints.jsHintProvider); selectHint(JSCodeHints.jsHintProvider, hintObj, "propA"); runs(function () { expect(fixPos(testEditor.getCursorPos())).toEqual(fixPos(end)); expect(testDoc.getRange(start, endplus)).toEqual("A1.propAprop"); expect(testDoc.getLine(end.line).length).toEqual(12); }); }); it("should insert, not replace, property hints with a partial current query", function () { var start = { line: 6, ch: 0 }, middle = { line: 6, ch: 6 }, end = { line: 6, ch: 8 }; testDoc.replaceRange("A1.pro", start, start); testEditor.setCursorPos(middle); var hintObj = expectHints(JSCodeHints.jsHintProvider); selectHint(JSCodeHints.jsHintProvider, hintObj, "propA"); runs(function () { expect(fixPos(testEditor.getCursorPos())).toEqual(fixPos(end)); expect(testDoc.getRange(start, end)).toEqual("A1.propA"); expect(testDoc.getLine(end.line).length).toEqual(8); }); }); it("should replace property hints replacing a partial current query", function () { var start = { line: 6, ch: 0 }, middle = { line: 6, ch: 6 }, end = { line: 6, ch: 8 }, endplus = { line: 6, ch: 10 }; testDoc.replaceRange("A1.propB", start, start); testEditor.setCursorPos(middle); var hintObj = expectHints(JSCodeHints.jsHintProvider); selectHint(JSCodeHints.jsHintProvider, hintObj, "propA"); runs(function () { expect(fixPos(testEditor.getCursorPos())).toEqual(fixPos(end)); expect(testDoc.getRange(start, endplus)).toEqual("A1.propApB"); expect(testDoc.getLine(end.line).length).toEqual(10); }); }); it("should replace property hints but not following delimiters", function () { var start = { line: 6, ch: 0 }, middle = { line: 6, ch: 4 }, end = { line: 6, ch: 9 }, endplus = { line: 6, ch: 14 }; testDoc.replaceRange("(A1.prop)", start, start); testEditor.setCursorPos(middle); var hintObj = expectHints(JSCodeHints.jsHintProvider); selectHint(JSCodeHints.jsHintProvider, hintObj, "propA"); runs(function () { expect(fixPos(testEditor.getCursorPos())).toEqual(fixPos(end)); expect(testDoc.getRange(start, endplus)).toEqual("(A1.propAprop)"); expect(testDoc.getLine(endplus.line).length).toEqual(14); }); }); it("should list hints for string, as string assigned to 's', 's' assigned to 'r' and 'r' assigned to 't'", function () { var start = { line: 26, ch: 0 }, middle = { line: 26, ch: 6 }; // pad spaces here as tern has issue,without space, no code hint testDoc.replaceRange(" t.", start); testEditor.setCursorPos(middle); var hintObj = expectHints(JSCodeHints.jsHintProvider); runs(function () { hintsPresentOrdered(hintObj, ["charAt", "charCodeAt", "concat", "indexOf"]); }); }); it("should list function type", function () { var start = { line: 37, ch: 0 }, middle = { line: 37, ch: 5 }; testDoc.replaceRange("funD(", start, start); testEditor.setCursorPos(middle); runs(function () { expectParameterHint([{name: "a", type: "String"}, {name: "b", type: "Number"}], 0); }); }); it("should list exports from a requirejs module", function () { var start = { line: 40, ch: 21 }; testEditor.setCursorPos(start); var hintObj = expectHints(JSCodeHints.jsHintProvider); runs(function () { hintsPresentExact(hintObj, ["a", "b", "c", "j"]); }); }); it("should list later defined property names", function () { var start = { line: 17, ch: 11 }; testEditor.setCursorPos(start); var hintObj = expectHints(JSCodeHints.jsHintProvider); runs(function () { hintsPresentExact(hintObj, ["foo", "propB"]); }); }); it("should list matching property names", function () { var cursor1 = { line: 12, ch: 0 }, cursor2 = { line: 12, ch: 6 }; testDoc.replaceRange("paramB", cursor1, cursor1); testEditor.setCursorPos(cursor2); var hintObj = expectHints(JSCodeHints.jsHintProvider); runs(function () { hintsPresentExact(hintObj, ["paramB1", "paramB2"]); }); }); it("should take anotation parameter type:String", function () { var start = { line: 37, ch: 21 }; testDoc.replaceRange("var k= funD(10,11).x.", start, start); testEditor.setCursorPos(start); var hintObj = expectHints(JSCodeHints.jsHintProvider); runs(function () { hintsPresentOrdered(hintObj, ["charAt", "charCodeAt", "concat", "indexOf"]); }); }); it("should take anotation parameter type:Number", function () { var start = { line: 37, ch: 21 }; testDoc.replaceRange("var k= funD(10,11).y.", start, start); testEditor.setCursorPos(start); var hintObj = expectHints(JSCodeHints.jsHintProvider); runs(function () { hintsPresentOrdered(hintObj, ["toExponential", "toFixed", "toString"]); }); }); it("should add new method on String .prototype", function () { var start = { line: 37, ch: 0 }; var testPos = { line: 40, ch: 12 }; testDoc.replaceRange("String.prototype.times = function (count) {\n" + "\treturn count < 1 ? '' : new Array[count + 1].join(this);\n};\n\"hello\".tim", start, start); testEditor.setCursorPos(testPos); var hintObj = expectHints(JSCodeHints.jsHintProvider); runs(function () { hintsPresentOrdered(hintObj, ["times", "trim"]); }); }); it("should list function defined from .prototype", function () { var start = { line: 59, ch: 5 }; testEditor.setCursorPos(start); var hintObj = expectHints(JSCodeHints.jsHintProvider); runs(function () { hintsPresentExact(hintObj, ["calc"]); }); }); it("should list function type defined from .prototype", function () { var start = { line: 59, ch: 10 }; testEditor.setCursorPos(start); runs(function () { expectParameterHint([{name: "a4", type: "Number"}, {name: "b4", type: "Number"}], 0); }); }); it("should list function inherited from super class", function () { var start = { line: 79, ch: 11 }; testEditor.setCursorPos(start); var hintObj = expectHints(JSCodeHints.jsHintProvider); runs(function () { hintsPresentExact(hintObj, ["amountDue", "getAmountDue", "getName", "name", "setAmountDue"]); }); }); it("should show argument from from .prototype.Method", function () { var start = { line: 80, ch: 0 }, testPos = { line: 80, ch: 24 }; testDoc.replaceRange("myCustomer.setAmountDue(", start); testEditor.setCursorPos(testPos); runs(function () { expectParameterHint([{name: "amountDue", type: "Object"}], 0); }); }); it("should show inner function type", function () { var testPos = { line: 96, ch: 23 }; testEditor.setCursorPos(testPos); runs(function () { expectParameterHint([{name: "arg", type: "String"}], 0); }); }); it("should show type for inner function returned function", function () { var testPos = { line: 96, ch: 33 }; testEditor.setCursorPos(testPos); expectHints(JSCodeHints.jsHintProvider); runs(function () { expectParameterHint([], 0); }); }); // parameter type anotation tests, due to another bug #3670: first argument has ? xit("should list parameter Date,boolean type", function () { var start = { line: 109, ch: 0 }, testPos = { line: 109, ch: 11 }; testDoc.replaceRange("funTypeAn1(", start); testEditor.setCursorPos(testPos); var hintObj = expectHints(JSCodeHints.jsHintProvider); runs(function () { hintsPresentExact(hintObj, ["funTypeAn1((a: bool, b: Date) -> {x, y}"]); }); }); // parameter type anotation tests, due to another bug #3670: first argument has ? xit("should list parameter function type and best guess for its argument/return types", function () { var testPos = { line: 123, ch: 11 }; testEditor.setCursorPos(testPos); runs(function () { expectParameterHint([{name: "f", type: "function(): number"}], 0); }); }); // parameter type annotation tests it("should list parameter function type and best guess for function call/return types", function () { var testPos = { line: 139, ch: 12 }; testEditor.setCursorPos(testPos); runs(function () { expectParameterHint([{name: "f", type: "function(String, Number):String"}], 0); }); }); it("should list array containing functions", function () { var testPos = { line: 142, ch: 7 }; testEditor.setCursorPos(testPos); var hintObj = expectHints(JSCodeHints.jsHintProvider); runs(function () { hintsPresent(hintObj, ["index1", "index2"]); }); }); it("should list function reference", function () { var start = { line: 144, ch: 0 }, testPos = { line: 144, ch: 14 }; testDoc.replaceRange("funArr.index1(", start); testEditor.setCursorPos(testPos); runs(function () { expectParameterHint([], 0); }); }); it("should insert hint as [\"my-key\"] since 'my-key' is not a valid property name", function () { var start = { line: 49, ch: 0 }, middle = { line: 49, ch: 5 }, end = { line: 49, ch: 13 }; testDoc.replaceRange("arr.m", start, start); testEditor.setCursorPos(middle); var hintObj = expectHints(JSCodeHints.jsHintProvider); selectHint(JSCodeHints.jsHintProvider, hintObj, "my-key"); runs(function () { expect(fixPos(testEditor.getCursorPos())).toEqual(fixPos(end)); expect(testDoc.getRange(start, end)).toEqual("arr[\"my-key\"]"); expect(testDoc.getLine(end.line).length).toEqual(13); }); }); it("should insert hint as [\"my-key\"] make sure this works if nothing is typed after the '.'", function () { var start = { line: 49, ch: 0 }, middle = { line: 49, ch: 4 }, end = { line: 49, ch: 13 }; testDoc.replaceRange("arr.", start, start); testEditor.setCursorPos(middle); var hintObj = expectHints(JSCodeHints.jsHintProvider); selectHint(JSCodeHints.jsHintProvider, hintObj, "my-key"); runs(function () { expect(fixPos(testEditor.getCursorPos())).toEqual(fixPos(end)); expect(testDoc.getRange(start, end)).toEqual("arr[\"my-key\"]"); expect(testDoc.getLine(end.line).length).toEqual(13); }); }); it("should insert hint as '.for' since keywords can be used as property names", function () { var start = { line: 49, ch: 0 }, middle = { line: 49, ch: 5 }, end = { line: 49, ch: 7 }; testDoc.replaceRange("arr.f", start, start); testEditor.setCursorPos(middle); var hintObj = expectHints(JSCodeHints.jsHintProvider); selectHint(JSCodeHints.jsHintProvider, hintObj, "for"); runs(function () { expect(fixPos(testEditor.getCursorPos())).toEqual(fixPos(end)); expect(testDoc.getRange(start, end)).toEqual("arr.for"); expect(testDoc.getLine(end.line).length).toEqual(7); }); }); it("should jump to function", function () { var start = { line: 43, ch: 0 }; testEditor.setCursorPos(start); runs(function () { editorJumped({line: 7, ch: 13}); }); }); it("should jump to var", function () { var start = { line: 44, ch: 10 }; testEditor.setCursorPos(start); runs(function () { editorJumped({line: 3, ch: 6}); }); }); it("should jump to closure, early defined var", function () { var start = { line: 17, ch: 9 }; testEditor.setCursorPos(start); runs(function () { editorJumped({line: 10, ch: 10}); }); }); it("should jump to the definition in new module file", function () { var start = { line: 40, ch: 22 }; testEditor.setCursorPos(start); runs(function () { editorJumped({line: 4, ch: 13, file: "MyModule.js"}); //jump to another file }); }); it("should jump to the method definition in .prototype", function () { var start = { line: 59, ch: 8 }; testEditor.setCursorPos(start); runs(function () { editorJumped({line: 53, ch: 21}); //jump to prototype.calc }); }); it("should jump to parameter passed in the method", function () { var start = { line: 63, ch: 20 }; testEditor.setCursorPos(start); runs(function () { editorJumped({line: 61, ch: 27}); }); }); it("should jump to parameter passed in anonymous method", function () { var start = { line: 83, ch: 25 }; testEditor.setCursorPos(start); runs(function () { editorJumped({line: 81, ch: 53}); }); }); it("should jump to inner method", function () { var start = { line: 96, ch: 32 }; testEditor.setCursorPos(start); runs(function () { editorJumped({line: 94, ch: 17}); }); }); it("should jump to the actual function definition, and not the exports line", function () { var start = { line: 159, ch: 22 }; testEditor.setCursorPos(start); runs(function () { editorJumped({line: 11, ch: 14, file: "MyModule.js"}); //jump to another file }); }); it("should not hint function, variable, or param decls", function () { var func = { line: 7, ch: 12 }, param = { line: 7, ch: 18 }, variable = { line: 10, ch: 10 }; runs(function () { testEditor.setCursorPos(func); expectNoParameterHint(); testEditor.setCursorPos(param); expectNoHints(JSCodeHints.jsHintProvider); testEditor.setCursorPos(variable); expectNoHints(JSCodeHints.jsHintProvider); }); }); it("should sort underscore names to the bottom", function () { testEditor.setCursorPos({ line: 146, ch: 0 }); var hintObj = expectHints(JSCodeHints.jsHintProvider); hintsPresentOrdered(hintObj, ["A1", "A2", "A3", "funB", "_A1"]); }); it("should list all properties for unknown type", function () { var start = { line: 149, ch: 0 }, end = { line: 149, ch: 5 }; testDoc.replaceRange("help.", start, start); testEditor.setCursorPos(end); var hintObj = expectHints(JSCodeHints.jsHintProvider); // check we have a properties from "Function", "String", and "Array" hintsPresentOrdered(hintObj, ["apply", "charCodeAt", "concat"]); }); it("should switch to guesses after typing a query that does not match any hints", function () { var start = { line: 150, ch: 0 }, end = { line: 150, ch: 5 }; testDoc.replaceRange("s.shift", start, start); testEditor.setCursorPos(end); var hintObj = expectHints(JSCodeHints.jsHintProvider); // check we have a properties that start with "shift" hintsPresentOrdered(hintObj, ["shift", "shiftKey"]); }); it("should handle valid non-ascii characters in a property name", function () { var start = { line: 153, ch: 0 }, end = { line: 153, ch: 13 }; testDoc.replaceRange("hope.frenchçP", start, start); testEditor.setCursorPos(end); var hintObj = expectHints(JSCodeHints.jsHintProvider); // check we have a properties that start with "shift" hintsPresentOrdered(hintObj, ["frenchçProp"]); }); it("should show guessed argument type from current passing parameter", function () { var start = { line: 80, ch: 0 }, testPos = { line: 80, ch: 24 }; testDoc.replaceRange("myCustomer.setAmountDue(10)", start); testEditor.setCursorPos(testPos); runs(function () { expectParameterHint([{name: "amountDue", type: "Number"}], 0); }); }); it("should list parameter hint for record type annotation", function () { var testPos = { line: 178, ch: 25 }; testEditor.setCursorPos(testPos); runs(function () { expectParameterHint([{name: "t", type: "{index: Number, name: String}"}], -1); }); }); it("should list parameter hint for optional parameters", function () { var testPos = { line: 214, ch: 17 }; testEditor.setCursorPos(testPos); runs(function () { expectParameterHint([{name: "a", type: "Number", isOptional: true}, {name: "b", type: "String", isOptional: true}], 0); }); }); it("should list parameter hint for a function parameter", function () { var testPos = { line: 181, ch: 12 }; testEditor.setCursorPos(testPos); runs(function () { expectParameterHint([{name: "compare", type: "function(Object, Object):Number", isOptional: true}], -1); }); }); it("should list parameter hint for an array parameter", function () { var testPos = { line: 184, ch: 12 }; testEditor.setCursorPos(testPos); runs(function () { expectParameterHint([{name: "other", type: "Array."}], -1); }); }); it("should list parameter hint for a source array annotation", function () { var testPos = { line: 200, ch: 20 }; testEditor.setCursorPos(testPos); runs(function () { expectParameterHint([{name: "a", type: "Array."}], 0); }); }); it("should close parameter hint when move off function", function () { var testPos = { line: 184, ch: 12 }, endPos = { line: 184, ch: 19 }; testEditor.setCursorPos(testPos); runs(function () { expectParameterHint([{name: "other", type: "Array."}], -1); }); runs(function () { testEditor.setCursorPos(endPos); expectParameterHintClosed(); }); }); it("should close parameter hint when move off function to another function", function () { var testPos = { line: 184, ch: 12 }, newPos = { line: 181, ch: 12 }; testEditor.setCursorPos(testPos); runs(function () { expectParameterHint([{name: "other", type: "Array."}], -1); }); runs(function () { testEditor.setCursorPos(newPos); expectParameterHintClosed(); }); }); it("should update current parameter as the cursor moves", function () { var testPos = { line: 186, ch: 19 }, newPos = { line: 186, ch: 20 }; testEditor.setCursorPos(testPos); runs(function () { expectParameterHint([{name: "char", type: "String"}, {name: "from", type: "Number", isOptional: true}], 0); }); runs(function () { testEditor.setCursorPos(newPos); expectParameterHint([{name: "char", type: "String"}, {name: "from", type: "Number", isOptional: true}], 1); }); }); // Test `jscodehints.noHintsOnDot` preference it("should consider dot a hintable key based on preference", function () { var noHintsOnDot = PreferencesManager.get("jscodehints.noHintsOnDot"); testEditor.setCursorPos({ line: 44, ch: 10 }); // Default is falsey expect(noHintsOnDot).toBeFalsy(); // Should get hints after dot expectHints(JSCodeHints.jsHintProvider, "."); // Set preference to true PreferencesManager.set("jscodehints.noHintsOnDot", true); // Should no longer get hints after dot expectNoHints(JSCodeHints.jsHintProvider, "."); // Set preference back to original value (converted to boolean) PreferencesManager.set("jscodehints.noHintsOnDot", !!noHintsOnDot); }); }); describe("JavaScript Code Hinting in a HTML file", function () { beforeEach(function () { setupTest(testHtmlPath, false); }); afterEach(function () { tearDownTest(); }); it("basic codehints in html file", function () { var start = { line: 37, ch: 9 }, end = { line: 37, ch: 13}; testDoc.replaceRange("x100.", start); testEditor.setCursorPos(end); var hintObj = expectHints(JSCodeHints.jsHintProvider); runs(function () { hintsPresentOrdered(hintObj, ["charAt", "charCodeAt", "concat", "indexOf"]); }); }); it("function type hint in html file", function () { var start = { line: 36, ch: 12 }; testEditor.setCursorPos(start); runs(function () { expectParameterHint([{name: "a", type: "Number"}], 0); }); }); it("should show function type code hint for function in script file inside html file", function () { var start = { line: 22, ch: 17 }; testEditor.setCursorPos(start); runs(function () { expectParameterHint([{name: "a", type: "String"}, {name: "b", type: "Number"}], 0); }); }); it("should show function type code hint for function in another script file inside html file", function () { var start = { line: 23, ch: 17 }; testEditor.setCursorPos(start); runs(function () { expectParameterHint([{name: "paramE1", type: "D1"}, {name: "paramE2", type: "Number"}], 0); }); }); it("should show global variable in another script file inside html file", function () { var start = { line: 27, ch: 8 }, end = { line: 27, ch: 13}, testPosStart = { line: 27, ch: 11}, testPosEnd = { line: 27, ch: 21}; testDoc.replaceRange("arr.m", start); testEditor.setCursorPos(end); var hintObj = expectHints(JSCodeHints.jsHintProvider); runs(function () { hintsPresent(hintObj, ["my-key"]); }); selectHint(JSCodeHints.jsHintProvider, hintObj, "my-key"); runs(function () { expect(testDoc.getRange(testPosStart, testPosEnd)).toEqual("[\"my-key\"]"); }); }); it("should jump to definition inside html file", function () { var start = { line: 36, ch: 10 }; testEditor.setCursorPos(start); runs(function () { editorJumped({line: 19, ch: 20}); }); }); it("should jump to funtion definition to loaded file1", function () { var start = { line: 22, ch: 15 }; testEditor.setCursorPos(start); runs(function () { editorJumped({line: 33, ch: 13}); }); }); it("should jump to funtion definition to loaded file2", function () { var start = { line: 23, ch: 15 }; testEditor.setCursorPos(start); runs(function () { editorJumped({line: 6, ch: 13}); }); }); it("should jump to property definition to loaded file1", function () { var start = { line: 23, ch: 28 }; testEditor.setCursorPos(start); runs(function () { editorJumped({line: 4, ch: 16}); }); }); it("should jump to property definition to loaded file2", function () { var start = { line: 23, ch: 18 }; testEditor.setCursorPos(start); runs(function () { editorJumped({line: 3, ch: 6}); }); }); }); describe("JavaScript Code Hinting without modules", function () { var testPath = extensionPath + "/unittest-files/non-module-test-files/app.js"; ScopeManager.handleProjectOpen(extensionPath + "/unittest-files/non-module-test-files/"); beforeEach(function () { setupTest(testPath, true); }); afterEach(function () { tearDownTest(); }); // Test reading multiple files and subdirectories // Turned for per #7646 xit("should handle reading all files when modules not used", function () { var start = { line: 8, ch: 8 }; runs(function () { testEditor.setCursorPos(start); var hintObj = expectHints(JSCodeHints.jsHintProvider); hintsPresentExact(hintObj, ["a", "b", "b1", "c", "d"]); }); }); }); describe("JavaScript Code Hinting with modules", function () { var testPath = extensionPath + "/unittest-files/module-test-files/module_tests.js"; beforeEach(function () { setupTest(testPath, true); }); afterEach(function () { tearDownTest(); }); it("should read methods created in submodule on this", function () { var start = { line: 8, ch: 17 }; runs(function () { testEditor.setCursorPos(start); var hintObj = expectHints(JSCodeHints.jsHintProvider); hintsPresentExact(hintObj, ["addMessage", "name", "privilegedMethod", "publicMethod1"]); }); }); // bug: wait for fix in tern xit("should read methods created in submodule", function () { var start = { line: 19, ch: 15 }; runs(function () { testEditor.setCursorPos(start); var hintObj = expectHints(JSCodeHints.jsHintProvider); hintsPresentExact(hintObj, ["addMessage", "name", "privilegedMethod", "publicMethod1"]); }); }); it("should read properties created in parent module", function () { var start = { line: 30, ch: 8 }, testPos = { line: 30, ch: 15}; testDoc.replaceRange("parent.", start); runs(function () { testEditor.setCursorPos(testPos); var hintObj = expectHints(JSCodeHints.jsHintProvider); hintsPresentExact(hintObj, ["addMessage", "name", "privilegedMethod", "publicMethod1"]); }); }); // bug: wait for tern xit("should read methods created in submodule module", function () { var start = { line: 62, ch: 0 }, testPos = { line: 62, ch: 13}; testDoc.replaceRange("SearchEngine.", start); runs(function () { testEditor.setCursorPos(testPos); var hintObj = expectHints(JSCodeHints.jsHintProvider); hintsPresentExact(hintObj, ["getYourLuckyNumber", "subSearch"]); }); }); it("should read methods created in parent module", function () { var start = { line: 78, ch: 41 }; runs(function () { testEditor.setCursorPos(start); var hintObj = expectHints(JSCodeHints.jsHintProvider); hintsPresentExact(hintObj, ["getYourLuckyNumber", "subSearch"]); }); }); it("should load module by file path from require", function () { var start = { line: 88, ch: 20 }; runs(function () { testEditor.setCursorPos(start); var hintObj = expectHints(JSCodeHints.jsHintProvider); hintsPresentExact(hintObj, ["color", "material", "size"]); }); }); // tern bug: https://github.com/marijnh/tern/issues/147 xit("should read properties from exported module", function () { var start = { line: 96, ch: 0 }, testPos = { line: 96, ch: 9}; testDoc.replaceRange("hondaCar.", start); runs(function () { testEditor.setCursorPos(testPos); var hintObj = expectHints(JSCodeHints.jsHintProvider); hintsPresentExact(hintObj, ["model", "name"]); }); }); // bug in test framework? can't run sequential jump, verification is wrong xit("should jump to a module, depending module", function () { var start = { line: 93, ch: 25 }, testPos = { line: 8, ch: 35 }; testEditor.setCursorPos(start); runs(function () { editorJumped({line: 5, ch: 23}); }); testEditor.setCursorPos(testPos); runs(function () { editorJumped({line: 5, ch: 23}); }); }); }); describe("JavaScript Code Hinting preference tests", function () { var testPath = extensionPath + "/unittest-files/preference-test-files/", preferences; function getPreferences(path) { preferences = null; FileSystem.resolve(path, function (err, file) { if (!err) { FileUtils.readAsText(file).done(function (text) { var configObj = null; try { configObj = JSON.parse(text); } catch (e) { // continue with null configObj console.log(e); } preferences = new Preferences(configObj); }).fail(function (error) { preferences = new Preferences(); }); } else { preferences = new Preferences(); } }); } // Test preferences file with no entries. Preferences should contain // default values. it("should handle reading an empty configuration file", function () { getPreferences(testPath + "defaults-test/.jscodehints"); waitsFor(function () { return preferences !== null; }); runs(function () { expect(preferences.getExcludedDirectories()).toEqual(/node_modules/); expect(preferences.getExcludedFiles().source). toBe(/^require.*\.js$|^jquery.*\.js$/.source); expect(preferences.getMaxFileCount()).toBe(100); expect(preferences.getMaxFileSize()).toBe(512 * 1024); }); }); // Test preferences file with empty or out of ranges values. Preferences // should contain default values. it("should handle reading an invalid configuration file", function () { getPreferences(testPath + "negative-test/.jscodehints"); waitsFor(function () { return preferences !== null; }); runs(function () { expect(preferences.getExcludedDirectories()).toEqual(/node_modules/); expect(preferences.getExcludedFiles().source). toBe(/^require.*\.js$|^jquery.*\.js$/.source); expect(preferences.getMaxFileCount()).toBe(100); expect(preferences.getMaxFileSize()).toBe(512 * 1024); }); }); // Positive test. Test pattern matching. it("should handle a valid configuration file", function () { getPreferences(testPath + "positive-test/.jscodehints"); waitsFor(function () { return preferences !== null; }); runs(function () { var excludedDirs = preferences.getExcludedDirectories(), excludedFiles = preferences.getExcludedFiles(); // test "excluded-dir1" expect(excludedDirs.test("excluded-dir1")).toBeTruthy(); expect(excludedDirs.test("xexcluded-dir1")).toBeFalsy(); // test "excluded-dir2-*" expect(excludedDirs.test("excluded-dir2-1")).toBeTruthy(); expect(excludedDirs.test("excluded-dir2-12")).toBeFalsy(); expect(excludedDirs.test("excluded-dir2-z")).toBeFalsy(); expect(excludedDirs.test("excluded-dir2-")).toBeFalsy(); expect(excludedDirs.test("xexcluded-dir2-1")).toBeFalsy(); // test "file1?.js" expect(excludedFiles.test("file1.js")).toBeTruthy(); expect(excludedFiles.test("file12.js")).toBeTruthy(); expect(excludedFiles.test("file123.js")).toBeFalsy(); // test "file2*.js" expect(excludedFiles.test("file2.js")).toBeTruthy(); expect(excludedFiles.test("file2xxx.js")).toBeTruthy(); expect(excludedFiles.test("filexxxx.js")).toBeFalsy(); // test "file3.js" expect(excludedFiles.test("file3.js")).toBeTruthy(); expect(excludedFiles.test("xfile3.js")).toBeFalsy(); // test "/file4[x|y|z]?.js/" expect(excludedFiles.test("file4.js")).toBeTruthy(); expect(excludedFiles.test("file4x.js")).toBeTruthy(); expect(excludedFiles.test("file4y.js")).toBeTruthy(); expect(excludedFiles.test("file4z.js")).toBeTruthy(); expect(excludedFiles.test("file4b.js")).toBeFalsy(); expect(excludedFiles.test("file4xyz.js")).toBeFalsy(); expect(excludedFiles.test("xfile4.js")).toBeTruthy(); // test builtin exclusions are also present expect(excludedFiles.test("require.js")).toBeTruthy(); expect(excludedFiles.test("jquery.js")).toBeTruthy(); expect(preferences.getMaxFileCount()).toBe(512); expect(preferences.getMaxFileSize()).toBe(100000); }); }); }); describe("regression tests", function () { it("should return true for valid identifier, false for invalid one", function () { var identifierList = ["ᾩ", "ĦĔĽĻŎ", "〱〱〱〱", "जावास्क्रि", "KingGeorgeⅦ", "π", "ಠ_ಠ", "price_9̶9̶_89", "$_3423", "TRUE", "FALSE", "IV"]; var invalidIdentifierList = [" break", "\tif", "\ntrade"]; invalidIdentifierList.forEach(function (element) { var result = HintUtils.maybeIdentifier(element); expect(result).toBe(false); }); identifierList.forEach(function (element) { var result = HintUtils.maybeIdentifier(element); expect(result).toBe(true); }); }); }); describe("JavaScript Code Hinting with test.html file", function () { var testFile = extensionPath + "/unittest-files/basic-test-files/test.html"; beforeEach(function () { setupTest(testFile, true); }); afterEach(function () { tearDownTest(); }); // FIXME (issue #3915) xit("should read function name has double byte chars", function () { var start = { line: 15, ch: 8 }, testPos = { line: 15, ch: 10 }; runs(function () { testEditor.setCursorPos(start); var hintObj = expectHints(JSCodeHints.jsHintProvider); hintsPresentExact(hintObj, ["fun測试"]); }); runs(function () { testEditor.setCursorPos(testPos); var hintObj = expectHints(JSCodeHints.jsHintProvider); hintsPresentExact(hintObj, ["fun測试()"]); }); }); it("should jump to function name with double byte chars", function () { var start = { line: 16, ch: 9 }; testEditor.setCursorPos(start); runs(function () { editorJumped({line: 12, ch: 20}); }); }); // FIXME (issue #3915) xit("should read function name has non ascii chars", function () { var start = { line: 16, ch: 16 }; runs(function () { testEditor.setCursorPos(start); var hintObj = expectHints(JSCodeHints.jsHintProvider); hintsPresentExact(hintObj, ["frenchçProp()"]); }); }); it("should jump to function name with non ascii chars", function () { var start = { line: 16, ch: 12 }; testEditor.setCursorPos(start); runs(function () { editorJumped({line: 12, ch: 20}); }); }); }); describe("Code Hinting Regression", function () { var testFile = extensionPath + "/unittest-files/module-test-files/china/cupFiller.js"; beforeEach(function () { setupTest(testFile, true); }); afterEach(function () { tearDownTest(); }); // The test is disabled, because the TernWorker will consult the ProjectManager to // determine all the files in the project root. We don't have a project root for this // testcase. Perhaps we need to change the testsetup or find another way of dealing with this // Test makes sure that http://github.com/adobe/brackets/issue/6931 doesn't show up xit("should show hints for members of referenced class", function () { var start = { line: 8, ch: 15 }; runs(function () { testEditor.setCursorPos(start); var hintObj = expectHints(JSCodeHints.jsHintProvider); hintsPresentExact(hintObj, ["empty", "emptyIt", "fill", "full"]); }); }); }); describe("JavaScript Code Hinting format parameters tests", function () { it("should format parameters with no params", function () { var params = []; expect(HintUtils2.formatParameterHint(params)).toBe(""); }); it("should format parameters with one param", function () { var params = [{name: "param1", type: "String"}]; expect(HintUtils2.formatParameterHint(params)).toBe("String param1"); }); it("should format parameters with one optional param", function () { var params = [{name: "param1", type: "String", isOptional: true}]; expect(HintUtils2.formatParameterHint(params)).toBe("[String param1]"); }); it("should format parameters with one required, one optional param", function () { var params = [{name: "param1", type: "String"}, {name: "param2", type: "String", isOptional: true}]; expect(HintUtils2.formatParameterHint(params)).toBe("String param1, [String param2]"); }); it("should format parameters with required param following an optional param", function () { var params = [{name: "param1", type: "String"}, {name: "param2", type: "String", isOptional: true}, {name: "param3", type: "String"}]; expect(HintUtils2.formatParameterHint(params)).toBe("String param1, [String param2, String param3]"); }); it("should format parameters with optional param following an optional param", function () { var params = [{name: "param1", type: "String"}, {name: "param2", type: "String", isOptional: true}, {name: "param3", type: "String", isOptional: true}]; expect(HintUtils2.formatParameterHint(params)).toBe("String param1, [String param2], [String param3]"); }); it("should format parameters with optional param following optional and required params", function () { var params = [{name: "param1", type: "String"}, {name: "param2", type: "String", isOptional: true}, {name: "param3", type: "String"}, {name: "param4", type: "String", isOptional: true}]; expect(HintUtils2.formatParameterHint(params)).toBe("String param1, [String param2, String param3], [String param4]"); }); }); }); }); ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/main.js ================================================ /* * Copyright (c) 2012 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ define(function (require, exports, module) { "use strict"; // Brackets modules var MultiRangeInlineEditor = brackets.getModule("editor/MultiRangeInlineEditor").MultiRangeInlineEditor, EditorManager = brackets.getModule("editor/EditorManager"), JSUtils = brackets.getModule("language/JSUtils"), LanguageManager = brackets.getModule("language/LanguageManager"), PerfUtils = brackets.getModule("utils/PerfUtils"), ProjectManager = brackets.getModule("project/ProjectManager"), Strings = brackets.getModule("strings"), HealthLogger = brackets.getModule("utils/HealthLogger"); /** * Return the token string that is at the specified position. * * @param hostEditor {!Editor} editor * @param {!{line:number, ch:number}} pos * @return {functionName: string, reason: string} */ function _getFunctionName(hostEditor, pos) { var token = hostEditor._codeMirror.getTokenAt(pos, true); // If the pos is at the beginning of a name, token will be the // preceding whitespace or dot. In that case, try the next pos. if (!/\S/.test(token.string) || token.string === ".") { token = hostEditor._codeMirror.getTokenAt({line: pos.line, ch: pos.ch + 1}, true); } // Return valid function expressions only (function call or reference) if (!((token.type === "variable") || (token.type === "variable-2") || (token.type === "property"))) { return { functionName: null, reason: Strings.ERROR_JSQUICKEDIT_FUNCTIONNOTFOUND }; } return { functionName: token.string, reason: null }; } /** * @private * For unit and performance tests. Allows lookup by function name instead of editor offset * without constructing an inline editor. * * @param {!string} functionName * @return {$.Promise} a promise that will be resolved with an array of function offset information */ function _findInProject(functionName) { var result = new $.Deferred(); PerfUtils.markStart(PerfUtils.JAVASCRIPT_FIND_FUNCTION); function _nonBinaryFileFilter(file) { return !LanguageManager.getLanguageForPath(file.fullPath).isBinary(); } ProjectManager.getAllFiles(_nonBinaryFileFilter) .done(function (files) { JSUtils.findMatchingFunctions(functionName, files) .done(function (functions) { PerfUtils.addMeasurement(PerfUtils.JAVASCRIPT_FIND_FUNCTION); result.resolve(functions); }) .fail(function () { PerfUtils.finalizeMeasurement(PerfUtils.JAVASCRIPT_FIND_FUNCTION); result.reject(); }); }) .fail(function () { result.reject(); }); return result.promise(); } /** * @private * For unit and performance tests. Allows lookup by function name instead of editor offset . * * @param {!Editor} hostEditor * @param {!string} functionName * @return {?$.Promise} synchronously resolved with an InlineWidget, or * {string} if js other than function is detected at pos, or * null if we're not ready to provide anything. */ function _createInlineEditor(hostEditor, functionName) { // Use Tern jump-to-definition helper, if it's available, to find InlineEditor target. var helper = brackets._jsCodeHintsHelper; if (helper === null) { return null; } var result = new $.Deferred(); PerfUtils.markStart(PerfUtils.JAVASCRIPT_INLINE_CREATE); var response = helper(); if (response.hasOwnProperty("promise")) { response.promise.done(function (jumpResp) { var resolvedPath = jumpResp.fullPath; if (resolvedPath) { // Tern doesn't always return entire function extent. // Use QuickEdit search now that we know which file to look at. var fileInfos = []; fileInfos.push({name: jumpResp.resultFile, fullPath: resolvedPath}); JSUtils.findMatchingFunctions(functionName, fileInfos, true) .done(function (functions) { if (functions && functions.length > 0) { var jsInlineEditor = new MultiRangeInlineEditor(functions); jsInlineEditor.load(hostEditor); PerfUtils.addMeasurement(PerfUtils.JAVASCRIPT_INLINE_CREATE); result.resolve(jsInlineEditor); } else { // No matching functions were found PerfUtils.addMeasurement(PerfUtils.JAVASCRIPT_INLINE_CREATE); result.reject(); } }) .fail(function () { PerfUtils.addMeasurement(PerfUtils.JAVASCRIPT_INLINE_CREATE); result.reject(); }); } else { // no result from Tern. Fall back to _findInProject(). _findInProject(functionName).done(function (functions) { if (functions && functions.length > 0) { var jsInlineEditor = new MultiRangeInlineEditor(functions); jsInlineEditor.load(hostEditor); PerfUtils.addMeasurement(PerfUtils.JAVASCRIPT_INLINE_CREATE); result.resolve(jsInlineEditor); } else { // No matching functions were found PerfUtils.addMeasurement(PerfUtils.JAVASCRIPT_INLINE_CREATE); result.reject(); } }).fail(function () { PerfUtils.finalizeMeasurement(PerfUtils.JAVASCRIPT_INLINE_CREATE); result.reject(); }); } }).fail(function () { PerfUtils.finalizeMeasurement(PerfUtils.JAVASCRIPT_INLINE_CREATE); result.reject(); }); } return result.promise(); } /** * This function is registered with EditorManager as an inline editor provider. It creates an inline editor * when the cursor is on a JavaScript function name, finds all functions that match the name * and shows (one/all of them) in an inline editor. * * @param {!Editor} editor * @param {!{line:number, ch:number}} pos * @return {$.Promise} a promise that will be resolved with an InlineWidget * or null if we're not ready to provide anything. */ function javaScriptFunctionProvider(hostEditor, pos) { // Only provide a JavaScript editor when cursor is in JavaScript content if (hostEditor.getModeForSelection() !== "javascript") { return null; } //Send analytics data for Quick Edit open HealthLogger.sendAnalyticsData( "QuickEditOpen", "usage", "quickEdit", "open" ); // Only provide JavaScript editor if the selection is within a single line var sel = hostEditor.getSelection(); if (sel.start.line !== sel.end.line) { return null; } // Always use the selection start for determining the function name. The pos // parameter is usually the selection end. var functionResult = _getFunctionName(hostEditor, sel.start); if (!functionResult.functionName) { return functionResult.reason || null; } return _createInlineEditor(hostEditor, functionResult.functionName); } // init EditorManager.registerInlineEditProvider(javaScriptFunctionProvider); PerfUtils.createPerfMeasurement("JAVASCRIPT_INLINE_CREATE", "JavaScript Inline Editor Creation"); PerfUtils.createPerfMeasurement("JAVASCRIPT_FIND_FUNCTION", "JavaScript Find Function"); // for unit tests only exports.javaScriptFunctionProvider = javaScriptFunctionProvider; exports._createInlineEditor = _createInlineEditor; exports._findInProject = _findInProject; }); ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/.editorconfig ================================================ ; This file is for unifying the coding style for different editors and IDEs. ; More information at http://EditorConfig.org root = true [grunt.js] indent_style = tab [ui/**.js] indent_style = tab [tests/unit/**.js] indent_style = tab ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/.jshintrc ================================================ { "curly": true, "eqnull": true, "eqeqeq": true, "expr": true, "latedef": true, "noarg": true, "onevar": true, "smarttabs": true, "trailing": true, "undef": true } ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/AUTHORS.txt ================================================ Authors ordered by first contribution A list of current team members is available at http://jqueryui.com/about Paul Bakaus Richard Worth Yehuda Katz Sean Catchpole John Resig Tane Piper Dmitri Gaskin Klaus Hartl Stefan Petre Gilles van den Hoven Micheil Smith Jörn Zaefferer Marc Grabanski Keith Wood Brandon Aaron Scott González Eduardo Lundgren Aaron Eisenberger Joan Piedra Bruno Basto Remy Sharp Bohdan Ganicky David Bolter Chi Cheng Ca-Phun Ung Ariel Flesler Maggie Costello Wachs Scott Jehl Todd Parker Andrew Powell Brant Burnett Douglas Neiner Paul Irish Ralph Whitbeck Thibault Duplessis Dominique Vincent Jack Hsu Adam Sontag Carl Fürstenberg Kevin Dalman Alberto Fernández Capel Jacek Jędrzejewski Ting Kuei Samuel Cormier-Iijima Jon Palmer Ben Hollis Justin MacCarthy Eyal Kobrigo Tiago Freire Diego Tres Holger Rüprich Ziling Zhao Mike Alsup Robson Braga Araujo Pierre-Henri Ausseil Christopher McCulloh Andrew Newcomb Lim Chee Aun Jorge Barreiro Daniel Steigerwald John Firebaugh John Enters Andrey Kapitcyn Dmitry Petrov Eric Hynds Chairat Sunthornwiphat Josh Varner Stéphane Raimbault Jay Merrifield J. Ryan Stinnett Peter Heiberg Alex Dovenmuehle Jamie Gegerson Raymond Schwartz Phillip Barnes Kyle Wilkinson Khaled AlHourani Marian Rudzynski Jean-Francois Remy Doug Blood Filippo Cavallarin Heiko Henning Aliaxandr Rahalevich Mario Visic Xavi Ramirez Max Schnur Saji Nediyanchath Corey Frang Aaron Peterson Ivan Peters Mohamed Cherif Bouchelaghem Marcos Sousa Michael DellaNoce George Marshall Tobias Brunner Martin Solli David Petersen Dan Heberden William Kevin Manire Gilmore Davidson Michael Wu Adam Parod Guillaume Gautreau Marcel Toele Dan Streetman Matt Hoskins Giovanni Giacobbi Kyle Florence Pavol Hluchý Hans Hillen Mark Johnson Trey Hunner Shane Whittet Edward Faulkner Adam Baratz Kato Kazuyoshi Eike Send Kris Borchers Eddie Monge Israel Tsadok Carson McDonald Jason Davies Garrison Locke David Murdoch Ben Boyle Jesse Baird Jonathan Vingiano Dylan Just Tomy Kaira Glenn Goodrich Ashek Elahi Ryan Neufeld Marc Neuwirth Philip Graham Benjamin Sterling Wesley Walser Kouhei Sutou Karl Kirch Chris Kelly Jay Oster Alex Polomoshnov David Leal igor milla Dave Methvin Florian Gutmann Marwan Al Jubeh Milan Broum Sebastian Sauer Gaëtan Muller Michel Weimerskirch William Griffiths Stojce Slavkovski David Soms David De Sloovere Michael P. Jung Shannon Pekary Matthew Hutton James Khoury Rob Loach Alberto Monteiro Alex Rhea Krzysztof Rosiński Ryan Olton Genie <386@mail.com> Rick Waldron Ian Simpson Lev Kitsis TJ VanToll Justin Domnitz Douglas Cerna Bert ter Heide Jasvir Nagra Petr Hromadko Harri Kilpiö Lado Lomidze Amir E. Aharoni Simon Sattes Jo Liss Guntupalli Karunakar Shahyar Ghobadpour Lukasz Lipinski Timo Tijhof Jason Moon Martin Frost Eneko Illarramendi EungJun Yi ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/GPL-LICENSE.txt ================================================ GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/MIT-LICENSE.txt ================================================ Copyright (c) 2012 Paul Bakaus, http://jqueryui.com/ This software consists of voluntary contributions made by many individuals (AUTHORS.txt, http://jqueryui.com/about) For exact contribution history, see the revision history and logs, available at http://jquery-ui.googlecode.com/svn/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/README.md ================================================ [jQuery UI](http://jqueryui.com/) - Interactions and Widgets for the web ================================ jQuery UI provides interactions like Drag and Drop and widgets like Autocomplete, Tabs and Slider and makes these as easy to use as jQuery itself. If you want to use jQuery UI, go to [jqueryui.com](http://jqueryui.com) to get started. Or visit the [Using jQuery UI Forum](http://forum.jquery.com/using-jquery-ui) for discussions and questions. If you are interested in helping develop jQuery UI, you are in the right place. To discuss development with team members and the community, visit the [Developing jQuery UI Forum](http://forum.jquery.com/developing-jquery-ui) or in #jquery on irc.freednode.net. For contributors --- If you want to help and provide a patch for a bugfix or new feature, please take a few minutes and look at [our Getting Involved guide](http://wiki.jqueryui.com/w/page/35263114/Getting-Involved). In particular check out the [Coding standards](http://wiki.jqueryui.com/w/page/12137737/Coding-standards) and [Commit Message Style Guide](http://wiki.jqueryui.com/w/page/25941597/Commit-Message-Style-Guide). In general, fork the project, create a branch for a specific change and send a pull request for that branch. Don't mix unrelated changes. You can use the commit message as the description for the pull request. Running the Unit Tests --- Run the unit tests with a local server that supports PHP. No database is required. Pre-configured php local servers are available for Windows and Mac. Here are some options: - Windows: [WAMP download](http://www.wampserver.com/en/) - Mac: [MAMP download](http://www.mamp.info/en/index.html) - Linux: [Setting up LAMP](https://www.linux.com/learn/tutorials/288158-easy-lamp-server-installation) - [Mongoose (most platforms)](http://code.google.com/p/mongoose/) Building jQuery UI --- jQuery UI uses the [grunt](http://github.com/cowboy/grunt) build system. Building jQuery UI requires node.js and a command line zip program. Install grunt. `npm install grunt -g` Clone the jQuery UI git repo. `git clone git://github.com/jquery/jquery-ui.git` `cd jquery-ui` Install node modules. `npm install` Run grunt. `grunt build` There are many other tasks that can be run through grunt. For a list of all tasks: `grunt --help` For committers --- When looking at pull requests, first check for [proper commit messages](http://wiki.jqueryui.com/w/page/12137724/Bug-Fixing-Guide). Do not merge pull requests directly through GitHub's interface. Most pull requests are a single commit; cherry-picking will avoid creating a merge commit. It's also common for contributors to make minor fixes in an additional one or two commits. These should be squashed before landing in master. **Make sure the author has a valid name and email address associated with the commit.** Fetch the remote first: git fetch [their-fork.git] [their-branch] Then cherry-pick the commit(s): git cherry-pick [sha-of-commit] If you need to edit the commit message: git cherry-pick -e [sha-of-commit] If you need to edit the changes: git cherry-pick -n [sha-of-commit] # make changes git commit --author="[author-name-and-email]" If it should go to the stable brach, cherry-pick it to stable: git checkout 1-8-stable git cherry-pick -x [sha-of-commit-from-master] *NOTE: Do not cherry-pick into 1-8-stable until you have pushed the commit from master upstream.* ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/build/release/changelog-shell ================================================ This file contains a shell for the changelog, followed by a list of every commit for this release. Choose the appropriate line for the Summary section. Move all commit notes to the appropriate section. - Each line should be in the following format: [Fixed|Added]: The ticket description. ([Ticket link], [Commit link]) - If the commit is not related to a bug or feature, e.g., whitepsace cleanup, remove it. - If there is no ticket number, search Trac for the relevant ticket. - If there is no ticket, create one (if needed), or leave just the commit link. Double check that "XXXX" does not appear anywhere in the changelog. Post this changelog at: CHANGELOG_URL DELETE EVERYTHING ABOVE THE FOLLOWING LINE ------------------------------------------ = Summary = This is the final release of jQuery UI 1.8. -- OR -- This is the second maintenance release for [[UI/Changelog/1.8|jQuery UI 1.8]]. = Build = = Core & Utilities = === UI Core === === Mouse === === Widget Factory === === Position === = Interactions = === Draggable === === Droppable === === Resizable === === Selectable === === Sortable === = Widgets = === Accordion === === Autocomplete === === Button === === Datepicker === === Dialog === === Progressbar === === Slider === === Tabs === = Effects = === Individual effects === = CSS Framework = = Demos = = Website = === Download Builder === ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/build/release/prepare-release ================================================ #!/bin/sh base_dir="`pwd`/jquery-ui-release" repo_dir="$base_dir/jquery-ui" release_dir="$repo_dir/build/release" github_repo="git@github.com:jquery/jquery-ui.git" remote_cmd="ssh jqadmin@ui-dev.jquery.com /srv/dev.jqueryui.com/prepare-release" # # Setup environment # echo echo "--------------------------" echo "| SETTING UP ENVIRONMENT |" echo "--------------------------" echo mkdir $base_dir cd $base_dir echo "Cloning repo from $github_repo..." git clone $github_repo cd $repo_dir echo echo "Environment setup complete." echo # # Figure out which versions we're dealing with # echo echo "------------------------" echo "| CALCULATING VERSIONS |" echo "------------------------" echo # NOTE: this will be different for minor and major releases version=`$remote_cmd/get-latest-version` major_minor=${version%.*} point=${version##*.} version_new="${major_minor}.$(($point + 1))" version_next=`cat version.txt` echo "We are going from $version to $version_new." echo "version.txt will be set to $version_next when complete." echo "Press enter to continue, or ctrl+c to cancel." read # # Generate shell for changelog # echo echo "------------------------" echo "| GENERATING CHANGELOG |" echo "------------------------" echo echo "Creating shell for changelog..." changelog_url="http:\/\/docs.jquery.com\/action\/edit\/UI\/Changelog\/$version_new" `sed "s/CHANGELOG_URL/$changelog_url/" <$release_dir/changelog-shell >$base_dir/changelog` # find all commits echo "Adding commits to changelog..." format_ticket='[http://dev.jqueryui.com/ticket/XXXX #XXXX]' format_commit='[http://github.com/jquery/jquery-ui/commit/%H %h]' format_full="* %s ($format_ticket, $format_commit)" git whatchanged $version... --pretty=format:"$format_full" \ -- ui themes demos build \ | sed '/^:/ d' \ | sed '/^$/ d' \ | sed 's/\(Fixe[sd] #\)\([0-9][0-9]*\)\(.*\)\(XXXX #XXXX\)/Fixed #\2\3\2 #\2/' \ | LC_ALL='C' sort -f \ >> $base_dir/changelog # find all fixed tickets echo "Adding Trac tickets to changelog..." $remote_cmd/generate-changelog >> $base_dir/changelog echo echo "Changelog complete." echo # # Generate list of contributors # echo echo "--------------------------" echo "| GATHERING CONTRIBUTORS |" echo "--------------------------" echo # find all committers and authors echo "Adding commiters and authors..." format_contributors='%aN%n%cN' git whatchanged $version... --pretty=format:"$format_contributors" \ | sed '/^:/ d' \ | sed '/^$/ d' \ > $base_dir/thankyou # find all reporters and commenters from Trac echo "Adding reporters and commenters from Trac..." $remote_cmd/generate-contributors >> $base_dir/thankyou # sort names echo "Sorting contributors..." LC_ALL='C' sort -f $base_dir/thankyou | uniq > $base_dir/_thankyou mv $base_dir/_thankyou $base_dir/thankyou # find all people that were thanked echo "Adding people thanked in commits..." git whatchanged $version... \ | grep -i thank \ >> $base_dir/thankyou echo echo "Find contributors from duplicates of fixed tickets and add them to:" echo "$base_dir/thankyou" echo "Press enter when done." read echo echo "Contributors list complete." echo # # Update version # echo echo "--------------------" echo "| UPDATING VERSION |" echo "--------------------" echo echo "Updating version.txt to $version_new..." echo $version_new > version.txt git commit -a -m "Tagging the $version_new release." version_new_time=`git log -1 --pretty=format:"%ad"` echo "Committed version.txt at $version_new_time..." echo "Tagging $version_new..." git tag $version_new echo "Updating version.txt to $version_next..." echo $version_next > version.txt git commit -a -m "Updating the master version to $version_next" echo "Committed version.txt..." echo echo "Version update complete." echo # # Push to GitHub # echo echo "---------------------" echo "| PUSHING TO GITHUB |" echo "---------------------" echo echo "Please review the output and generated files as a sanity check." echo "Press enter to continue or ctrl+c to abort." read git push git push --tags echo echo "Push to GitHub complete." echo # # Update Trac # echo echo "-----------------" echo "| UPDATING TRAC |" echo "-----------------" echo # TODO: automate this # NOTE: this will be different for minor and major releases milestone=`$remote_cmd/get-latest-milestone` # Create new milestrone and version echo "$version_new was tagged at $version_new_time." echo "Create and close the $version_new Milestone with the above date and time." echo "Create the $version_new Version with the above date and time." echo "Press enter when done." read # Update milestone for all fixed tickets echo "Change all $milestone fixed tickets to $version_new." echo "Press enter when done." read echo echo "Trac updates complete." echo # # Build jQuery UI # echo echo "----------------------" echo "| BUILDING JQUERY UI |" echo "----------------------" echo # check out the tagged version echo "Checking out $version_new..." git checkout $version_new cd build # Update the link to the docs (never contains the patch version) echo "Updating URL for API docs..." sed "s/UI\/API\/\${release\.version}/UI\/API\/$major_minor/" build.xml >build.xml.tmp mv build.xml.tmp build.xml # Run the build echo "Running build..." ant echo echo "Build complete." echo # # Upload zip to Google Code # echo echo "----------------------" echo "| UPDATE GOOGLE CODE |" echo "----------------------" echo echo "Upload zip to Google Code." echo " http://code.google.com/p/jquery-ui/downloads/entry" echo " Summary: jQuery UI $version_new (Source, demos, docs, themes, tests) STABLE" echo " Labels: Featured, Type-Source, OpSys-All" echo "Modify the previous release to no longer say STABLE at the end." echo "Remove the featured label from the previous release." echo "Press enter when done." read echo echo "Google Code update complete." echo # # Update SVN # echo echo "----------------" echo "| UPDATING SVN |" echo "----------------" echo cd $base_dir mkdir svn cd svn echo "Checking out SVN tags..." svn co --depth immediates https://jquery-ui.googlecode.com/svn/tags cd tags echo "Unzipping build into tags/$version_new..." unzip $repo_dir/build/dist/jquery-ui-$version_new.zip mv jquery-ui-$version_new $version_new echo "Adding files to SVN..." svn add $version_new echo "Setting svn:mime-type..." find $version_new -name \*.js -exec svn propset svn:mime-type text/javascript {} \; find $version_new -name \*.css -exec svn propset svn:mime-type text/css {} \; find $version_new -name \*.html -exec svn propset svn:mime-type text/html {} \; find $version_new -name \*.png -exec svn propset svn:mime-type image/png {} \; find $version_new -name \*.gif -exec svn propset svn:mime-type image/gif {} \; # TODO: commit echo echo "svn commit with the following message:" echo "Created $version_new tag from http://jquery-ui.googlecode.com/files/jquery-ui-$version_new.zip" echo "Press enter when done." read echo echo "SVN update complete." echo # # Generate themes # # ruby -e 'puts File.read("thankyou").split("\n").join(", ")' > thankyou2 ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/build/tasks/build.js ================================================ module.exports = function( grunt ) { var path = require( "path" ); grunt.registerMultiTask( "copy", "Copy files to destination folder and replace @VERSION with pkg.version", function() { function replaceVersion( source ) { return source.replace( /@VERSION/g, grunt.config( "pkg.version" ) ); } function copyFile( src, dest ) { if ( /(js|css)$/.test( src ) ) { grunt.file.copy( src, dest, { process: replaceVersion }); } else { grunt.file.copy( src, dest ); } } var files = grunt.file.expandFiles( this.file.src ), target = this.file.dest + "/", strip = this.data.strip, renameCount = 0, fileName; if ( typeof strip === "string" ) { strip = new RegExp( "^" + grunt.template.process( strip, grunt.config() ).replace( /[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&" ) ); } files.forEach(function( fileName ) { var targetFile = strip ? fileName.replace( strip, "" ) : fileName; copyFile( fileName, target + targetFile ); }); grunt.log.writeln( "Copied " + files.length + " files." ); for ( fileName in this.data.renames ) { renameCount += 1; copyFile( fileName, target + grunt.template.process( this.data.renames[ fileName ], grunt.config() ) ); } if ( renameCount ) { grunt.log.writeln( "Renamed " + renameCount + " files." ); } }); grunt.registerMultiTask( "zip", "Create a zip file for release", function() { // TODO switch back to adm-zip for better cross-platform compability once it actually works // 0.1.3 works, but result can't be unzipped // its also a lot slower then zip program, probably due to how its used... // var files = grunt.file.expandFiles( "dist/" + this.file.src + "/**/*" ); // grunt.log.writeln( "Creating zip file " + this.file.dest ); //var AdmZip = require( "adm-zip" ); //var zip = new AdmZip(); //files.forEach(function( file ) { // grunt.verbose.writeln( "Zipping " + file ); // // rewrite file names from dist folder (created by build), drop the /dist part // zip.addFile(file.replace(/^dist/, "" ), fs.readFileSync( file ) ); //}); //zip.writeZip( "dist/" + this.file.dest ); //grunt.log.writeln( "Wrote " + files.length + " files to " + this.file.dest ); var done = this.async(), dest = this.file.dest, src = grunt.template.process( this.file.src, grunt.config() ); grunt.utils.spawn({ cmd: "zip", args: [ "-r", dest, src ], opts: { cwd: 'dist' } }, function( err, result ) { if ( err ) { grunt.log.error( err ); done(); return; } grunt.log.writeln( "Zipped " + dest ); done(); }); }); grunt.registerMultiTask( "md5", "Create list of md5 hashes for CDN uploads", function() { // remove dest file before creating it, to make sure itself is not included if ( path.existsSync( this.file.dest ) ) { fs.unlinkSync( this.file.dest ); } var crypto = require( "crypto" ), dir = this.file.src + "/", hashes = []; grunt.file.expandFiles( dir + "**/*" ).forEach(function( fileName ) { var hash = crypto.createHash( "md5" ); hash.update( grunt.file.read( fileName, "ascii" ) ); hashes.push( fileName.replace( dir, "" ) + " " + hash.digest( "hex" ) ); }); grunt.file.write( this.file.dest, hashes.join( "\n" ) + "\n" ); grunt.log.writeln( "Wrote " + this.file.dest + " with " + hashes.length + " hashes" ); }); // only needed for 1.8 grunt.registerTask( "download_docs", function() { function capitalize(value) { return value[0].toUpperCase() + value.slice(1); } // should be grunt.config("pkg.version")? var version = "1.8", docsDir = "dist/docs", files = "draggable droppable resizable selectable sortable accordion autocomplete button datepicker dialog progressbar slider tabs position" .split(" ").map(function(widget) { return { url: "http://docs.jquery.com/action/render/UI/API/" + version + "/" + capitalize(widget), dest: docsDir + '/' + widget + '.html' }; }); files = files.concat("animate addClass effect hide removeClass show switchClass toggle toggleClass".split(" ").map(function(widget) { return { url: "http://docs.jquery.com/action/render/UI/Effects/" + widget, dest: docsDir + '/' + widget + '.html' }; })); files = files.concat("Blind Clip Drop Explode Fade Fold Puff Slide Scale Bounce Highlight Pulsate Shake Size Transfer".split(" ").map(function(widget) { return { url: "http://docs.jquery.com/action/render/UI/Effects/" + widget, dest: docsDir + '/effect-' + widget.toLowerCase() + '.html' }; })); grunt.file.mkdir( "dist/docs" ); grunt.utils.async.forEach( files, function( file, done ) { var out = fs.createWriteStream( file.dest ); out.on( "close", done ); request( file.url ).pipe( out ); }, this.async() ); }); grunt.registerTask( "download_themes", function() { // var AdmZip = require('adm-zip'); var done = this.async(), themes = grunt.file.read( "build/themes" ).split(","), requests = 0; grunt.file.mkdir( "dist/tmp" ); themes.forEach(function( theme, index ) { requests += 1; grunt.file.mkdir( "dist/tmp/" + index ); var zipFileName = "dist/tmp/" + index + ".zip", out = fs.createWriteStream( zipFileName ); out.on( "close", function() { grunt.log.writeln( "done downloading " + zipFileName ); // TODO AdmZip produces "crc32 checksum failed", need to figure out why // var zip = new AdmZip(zipFileName); // zip.extractAllTo('dist/tmp/' + index + '/'); // until then, using cli unzip... grunt.utils.spawn({ cmd: "unzip", args: [ "-d", "dist/tmp/" + index, zipFileName ] }, function( err, result ) { grunt.log.writeln( "Unzipped " + zipFileName + ", deleting it now" ); fs.unlinkSync( zipFileName ); requests -= 1; if (requests === 0) { done(); } }); }); request( "http://ui-dev.jquery.com/download/?" + theme ).pipe( out ); }); }); grunt.registerTask( "copy_themes", function() { // each package includes the base theme, ignore that var filter = /themes\/base/, files = grunt.file.expandFiles( "dist/tmp/*/development-bundle/themes/**/*" ).filter(function( file ) { return !filter.test( file ); }), // TODO the grunt.template.process call shouldn't be necessary target = "dist/" + grunt.template.process( grunt.config( "files.themes" ), grunt.config() ) + "/", distFolder = "dist/" + grunt.template.process( grunt.config( "files.dist" ), grunt.config() ); files.forEach(function( fileName ) { var targetFile = fileName.replace( /dist\/tmp\/\d+\/development-bundle\//, "" ).replace( "jquery-ui-.custom", "jquery-ui" ); grunt.file.copy( fileName, target + targetFile ); }); // copy minified base theme from regular release files = grunt.file.expandFiles( distFolder + "/themes/base/**/*" ); files.forEach(function( fileName ) { grunt.file.copy( fileName, target + fileName.replace( distFolder, "" ) ); }); }); grunt.registerTask( "clean", function() { require( "rimraf" ).sync( "dist" ); }); grunt.registerTask( "authors", function() { var done = this.async(); grunt.utils.spawn({ cmd: "git", args: [ "log", "--pretty=%an <%ae>" ] }, function( err, result ) { if ( err ) { grunt.log.error( err ); return done( false ); } var authors, tracked = {}; authors = result.split( "\n" ).reverse().filter(function( author ) { var first = !tracked[ author ]; tracked[ author ] = true; return first; }).join( "\n" ); grunt.log.writeln( authors ); done(); }); }); }; ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/build/tasks/testswarm.js ================================================ module.exports = function( grunt ) { grunt.registerTask( "testswarm", function( commit, configFile ) { var test, testswarm = require( "testswarm" ), config = grunt.file.readJSON( configFile ).jqueryui, testBase = "http://swarm.jquery.org/git/jquery-ui/" + commit + "/tests/unit/", testUrls = [], tests = { "Accordion": "accordion/accordion.html", "Accordion_deprecated": "accordion/accordion_deprecated.html", "Autocomplete": "autocomplete/autocomplete.html", "Button": "button/button.html", "Core": "core/core.html", //"datepicker/datepicker.html", //"dialog/dialog.html", //"draggable/draggable.html", //"droppable/droppable.html", "Effects": "effects/effects.html", "Menu": "menu/menu.html", "Position": "position/position.html", "Position_deprecated": "position/position_deprecated.html", "Progressbar": "progressbar/progressbar.html", //"resizable/resizable.html", //"selectable/selectable.html", //"slider/slider.html", //"sortable/sortable.html", "Spinner": "spinner/spinner.html", "Tabs": "tabs/tabs.html", "Tabs_deprecated": "tabs/tabs_deprecated.html", "Tooltip": "tooltip/tooltip.html", "Widget": "widget/widget.html" }; for ( test in tests ) { testUrls.push( testBase + tests[ test ] + "?nojshint=true" ); } testswarm({ url: "http://swarm.jquery.org/", pollInterval: 10000, timeout: 1000 * 60 * 30, done: this.async() }, { authUsername: "jqueryui", authToken: config.authToken, jobName: 'jQuery UI commit #' + commit.substr( 0, 10 ) + '', runMax: config.runMax, "runNames[]": Object.keys(tests), "runUrls[]": testUrls, "browserSets[]": ["popular"] }); }); }; ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/build/themes ================================================ download=true&files%5B%5D=ui.core.js&files%5B%5D=ui.widget.js&files%5B%5D=ui.mouse.js&files%5B%5D=ui.position.js&files%5B%5D=ui.draggable.js&files%5B%5D=ui.droppable.js&files%5B%5D=ui.resizable.js&files%5B%5D=ui.selectable.js&files%5B%5D=ui.sortable.js&files%5B%5D=ui.accordion.js&files%5B%5D=ui.autocomplete.js&files%5B%5D=ui.button.js&files%5B%5D=ui.dialog.js&files%5B%5D=ui.slider.js&files%5B%5D=ui.tabs.js&files%5B%5D=ui.datepicker.js&files%5B%5D=ui.progressbar.js&files%5B%5D=effects.core.js&files%5B%5D=effects.blind.js&files%5B%5D=effects.bounce.js&files%5B%5D=effects.clip.js&files%5B%5D=effects.drop.js&files%5B%5D=effects.explode.js&files%5B%5D=effects.fold.js&files%5B%5D=effects.highlight.js&files%5B%5D=effects.pulsate.js&files%5B%5D=effects.scale.js&files%5B%5D=effects.shake.js&files%5B%5D=effects.slide.js&files%5B%5D=effects.transfer.js&theme=%3FffDefault%3DTrebuchet%2BMS%2C%2BTahoma%2C%2BVerdana%2C%2BArial%2C%2Bsans-serif%26fwDefault%3Dbold%26fsDefault%3D1.1em%26cornerRadius%3D4px%26bgColorHeader%3Df6a828%26bgTextureHeader%3D12_gloss_wave.png%26bgImgOpacityHeader%3D35%26borderColorHeader%3De78f08%26fcHeader%3Dffffff%26iconColorHeader%3Dffffff%26bgColorContent%3Deeeeee%26bgTextureContent%3D03_highlight_soft.png%26bgImgOpacityContent%3D100%26borderColorContent%3Ddddddd%26fcContent%3D333333%26iconColorContent%3D222222%26bgColorDefault%3Df6f6f6%26bgTextureDefault%3D02_glass.png%26bgImgOpacityDefault%3D100%26borderColorDefault%3Dcccccc%26fcDefault%3D1c94c4%26iconColorDefault%3Def8c08%26bgColorHover%3Dfdf5ce%26bgTextureHover%3D02_glass.png%26bgImgOpacityHover%3D100%26borderColorHover%3Dfbcb09%26fcHover%3Dc77405%26iconColorHover%3Def8c08%26bgColorActive%3Dffffff%26bgTextureActive%3D02_glass.png%26bgImgOpacityActive%3D65%26borderColorActive%3Dfbd850%26fcActive%3Deb8f00%26iconColorActive%3Def8c08%26bgColorHighlight%3Dffe45c%26bgTextureHighlight%3D03_highlight_soft.png%26bgImgOpacityHighlight%3D75%26borderColorHighlight%3Dfed22f%26fcHighlight%3D363636%26iconColorHighlight%3D228ef1%26bgColorError%3Db81900%26bgTextureError%3D08_diagonals_thick.png%26bgImgOpacityError%3D18%26borderColorError%3Dcd0a0a%26fcError%3Dffffff%26iconColorError%3Dffd27a%26bgColorOverlay%3D666666%26bgTextureOverlay%3D08_diagonals_thick.png%26bgImgOpacityOverlay%3D20%26opacityOverlay%3D50%26bgColorShadow%3D000000%26bgTextureShadow%3D01_flat.png%26bgImgOpacityShadow%3D10%26opacityShadow%3D20%26thicknessShadow%3D5px%26offsetTopShadow%3D-5px%26offsetLeftShadow%3D-5px%26cornerRadiusShadow%3D5px&scope=&t-name=ui-lightness&ui-version=1.8.2,download=true&files%5B%5D=ui.core.js&files%5B%5D=ui.widget.js&files%5B%5D=ui.mouse.js&files%5B%5D=ui.position.js&files%5B%5D=ui.draggable.js&files%5B%5D=ui.droppable.js&files%5B%5D=ui.resizable.js&files%5B%5D=ui.selectable.js&files%5B%5D=ui.sortable.js&files%5B%5D=ui.accordion.js&files%5B%5D=ui.autocomplete.js&files%5B%5D=ui.button.js&files%5B%5D=ui.dialog.js&files%5B%5D=ui.slider.js&files%5B%5D=ui.tabs.js&files%5B%5D=ui.datepicker.js&files%5B%5D=ui.progressbar.js&files%5B%5D=effects.core.js&files%5B%5D=effects.blind.js&files%5B%5D=effects.bounce.js&files%5B%5D=effects.clip.js&files%5B%5D=effects.drop.js&files%5B%5D=effects.explode.js&files%5B%5D=effects.fold.js&files%5B%5D=effects.highlight.js&files%5B%5D=effects.pulsate.js&files%5B%5D=effects.scale.js&files%5B%5D=effects.shake.js&files%5B%5D=effects.slide.js&files%5B%5D=effects.transfer.js&theme=%3FffDefault%3DSegoe%2BUI%252C%2BArial%252C%2Bsans-serif%26fwDefault%3Dbold%26fsDefault%3D1.1em%26cornerRadius%3D6px%26bgColorHeader%3D333333%26bgTextureHeader%3D12_gloss_wave.png%26bgImgOpacityHeader%3D25%26borderColorHeader%3D333333%26fcHeader%3Dffffff%26iconColorHeader%3Dffffff%26bgColorContent%3D000000%26bgTextureContent%3D05_inset_soft.png%26bgImgOpacityContent%3D25%26borderColorContent%3D666666%26fcContent%3Dffffff%26iconColorContent%3Dcccccc%26bgColorDefault%3D555555%26bgTextureDefault%3D02_glass.png%26bgImgOpacityDefault%3D20%26borderColorDefault%3D666666%26fcDefault%3Deeeeee%26iconColorDefault%3Dcccccc%26bgColorHover%3D0078a3%26bgTextureHover%3D02_glass.png%26bgImgOpacityHover%3D40%26borderColorHover%3D59b4d4%26fcHover%3Dffffff%26iconColorHover%3Dffffff%26bgColorActive%3Df58400%26bgTextureActive%3D05_inset_soft.png%26bgImgOpacityActive%3D30%26borderColorActive%3Dffaf0f%26fcActive%3Dffffff%26iconColorActive%3D222222%26bgColorHighlight%3Deeeeee%26bgTextureHighlight%3D03_highlight_soft.png%26bgImgOpacityHighlight%3D80%26borderColorHighlight%3Dcccccc%26fcHighlight%3D2e7db2%26iconColorHighlight%3D4b8e0b%26bgColorError%3Dffc73d%26bgTextureError%3D02_glass.png%26bgImgOpacityError%3D40%26borderColorError%3Dffb73d%26fcError%3D111111%26iconColorError%3Da83300%26bgColorOverlay%3D5c5c5c%26bgTextureOverlay%3D01_flat.png%26bgImgOpacityOverlay%3D50%26opacityOverlay%3D80%26bgColorShadow%3Dcccccc%26bgTextureShadow%3D01_flat.png%26bgImgOpacityShadow%3D30%26opacityShadow%3D60%26thicknessShadow%3D7px%26offsetTopShadow%3D-7px%26offsetLeftShadow%3D-7px%26cornerRadiusShadow%3D8px&scope=&t-name=ui-darkness&ui-version=1.8.2,download=true&files%5B%5D=ui.core.js&files%5B%5D=ui.widget.js&files%5B%5D=ui.mouse.js&files%5B%5D=ui.position.js&files%5B%5D=ui.draggable.js&files%5B%5D=ui.droppable.js&files%5B%5D=ui.resizable.js&files%5B%5D=ui.selectable.js&files%5B%5D=ui.sortable.js&files%5B%5D=ui.accordion.js&files%5B%5D=ui.autocomplete.js&files%5B%5D=ui.button.js&files%5B%5D=ui.dialog.js&files%5B%5D=ui.slider.js&files%5B%5D=ui.tabs.js&files%5B%5D=ui.datepicker.js&files%5B%5D=ui.progressbar.js&files%5B%5D=effects.core.js&files%5B%5D=effects.blind.js&files%5B%5D=effects.bounce.js&files%5B%5D=effects.clip.js&files%5B%5D=effects.drop.js&files%5B%5D=effects.explode.js&files%5B%5D=effects.fold.js&files%5B%5D=effects.highlight.js&files%5B%5D=effects.pulsate.js&files%5B%5D=effects.scale.js&files%5B%5D=effects.shake.js&files%5B%5D=effects.slide.js&files%5B%5D=effects.transfer.js&theme=%3FffDefault%3DVerdana%2CArial%2Csans-serif%26fwDefault%3Dnormal%26fsDefault%3D1.1em%26cornerRadius%3D4px%26bgColorHeader%3Dcccccc%26bgTextureHeader%3D03_highlight_soft.png%26bgImgOpacityHeader%3D75%26borderColorHeader%3Daaaaaa%26fcHeader%3D222222%26iconColorHeader%3D222222%26bgColorContent%3Dffffff%26bgTextureContent%3D01_flat.png%26bgImgOpacityContent%3D75%26borderColorContent%3Daaaaaa%26fcContent%3D222222%26iconColorContent%3D222222%26bgColorDefault%3De6e6e6%26bgTextureDefault%3D02_glass.png%26bgImgOpacityDefault%3D75%26borderColorDefault%3Dd3d3d3%26fcDefault%3D555555%26iconColorDefault%3D888888%26bgColorHover%3Ddadada%26bgTextureHover%3D02_glass.png%26bgImgOpacityHover%3D75%26borderColorHover%3D999999%26fcHover%3D212121%26iconColorHover%3D454545%26bgColorActive%3Dffffff%26bgTextureActive%3D02_glass.png%26bgImgOpacityActive%3D65%26borderColorActive%3Daaaaaa%26fcActive%3D212121%26iconColorActive%3D454545%26bgColorHighlight%3Dfbf9ee%26bgTextureHighlight%3D02_glass.png%26bgImgOpacityHighlight%3D55%26borderColorHighlight%3Dfcefa1%26fcHighlight%3D363636%26iconColorHighlight%3D2e83ff%26bgColorError%3Dfef1ec%26bgTextureError%3D02_glass.png%26bgImgOpacityError%3D95%26borderColorError%3Dcd0a0a%26fcError%3Dcd0a0a%26iconColorError%3Dcd0a0a%26bgColorOverlay%3Daaaaaa%26bgTextureOverlay%3D01_flat.png%26bgImgOpacityOverlay%3D0%26opacityOverlay%3D30%26bgColorShadow%3Daaaaaa%26bgTextureShadow%3D01_flat.png%26bgImgOpacityShadow%3D0%26opacityShadow%3D30%26thicknessShadow%3D8px%26offsetTopShadow%3D-8px%26offsetLeftShadow%3D-8px%26cornerRadiusShadow%3D8px&scope=&t-name=smoothness&ui-version=1.8.2,download=true&files%5B%5D=ui.core.js&files%5B%5D=ui.widget.js&files%5B%5D=ui.mouse.js&files%5B%5D=ui.position.js&files%5B%5D=ui.draggable.js&files%5B%5D=ui.droppable.js&files%5B%5D=ui.resizable.js&files%5B%5D=ui.selectable.js&files%5B%5D=ui.sortable.js&files%5B%5D=ui.accordion.js&files%5B%5D=ui.autocomplete.js&files%5B%5D=ui.button.js&files%5B%5D=ui.dialog.js&files%5B%5D=ui.slider.js&files%5B%5D=ui.tabs.js&files%5B%5D=ui.datepicker.js&files%5B%5D=ui.progressbar.js&files%5B%5D=effects.core.js&files%5B%5D=effects.blind.js&files%5B%5D=effects.bounce.js&files%5B%5D=effects.clip.js&files%5B%5D=effects.drop.js&files%5B%5D=effects.explode.js&files%5B%5D=effects.fold.js&files%5B%5D=effects.highlight.js&files%5B%5D=effects.pulsate.js&files%5B%5D=effects.scale.js&files%5B%5D=effects.shake.js&files%5B%5D=effects.slide.js&files%5B%5D=effects.transfer.js&theme=%3FffDefault%3DVerdana%252CArial%252Csans-serif%26fwDefault%3Dnormal%26fsDefault%3D1.1em%26cornerRadius%3D5px%26bgColorHeader%3D2191c0%26bgTextureHeader%3D12_gloss_wave.png%26bgImgOpacityHeader%3D75%26borderColorHeader%3D4297d7%26fcHeader%3Deaf5f7%26iconColorHeader%3Dd8e7f3%26bgColorContent%3Dfcfdfd%26bgTextureContent%3D06_inset_hard.png%26bgImgOpacityContent%3D100%26borderColorContent%3Da6c9e2%26fcContent%3D222222%26iconColorContent%3D0078ae%26bgColorDefault%3D0078ae%26bgTextureDefault%3D02_glass.png%26bgImgOpacityDefault%3D45%26borderColorDefault%3D77d5f7%26fcDefault%3Dffffff%26iconColorDefault%3De0fdff%26bgColorHover%3D79c9ec%26bgTextureHover%3D02_glass.png%26bgImgOpacityHover%3D75%26borderColorHover%3D448dae%26fcHover%3D026890%26iconColorHover%3D056b93%26bgColorActive%3D6eac2c%26bgTextureActive%3D12_gloss_wave.png%26bgImgOpacityActive%3D50%26borderColorActive%3Dacdd4a%26fcActive%3Dffffff%26iconColorActive%3Df5e175%26bgColorHighlight%3Df8da4e%26bgTextureHighlight%3D02_glass.png%26bgImgOpacityHighlight%3D55%26borderColorHighlight%3Dfcd113%26fcHighlight%3D915608%26iconColorHighlight%3Df7a50d%26bgColorError%3De14f1c%26bgTextureError%3D12_gloss_wave.png%26bgImgOpacityError%3D45%26borderColorError%3Dcd0a0a%26fcError%3Dffffff%26iconColorError%3Dfcd113%26bgColorOverlay%3Daaaaaa%26bgTextureOverlay%3D01_flat.png%26bgImgOpacityOverlay%3D75%26opacityOverlay%3D30%26bgColorShadow%3D999999%26bgTextureShadow%3D01_flat.png%26bgImgOpacityShadow%3D55%26opacityShadow%3D45%26thicknessShadow%3D0px%26offsetTopShadow%3D5px%26offsetLeftShadow%3D5px%26cornerRadiusShadow%3D5px&scope=&t-name=start&ui-version=1.8.2,download=true&files%5B%5D=ui.core.js&files%5B%5D=ui.widget.js&files%5B%5D=ui.mouse.js&files%5B%5D=ui.position.js&files%5B%5D=ui.draggable.js&files%5B%5D=ui.droppable.js&files%5B%5D=ui.resizable.js&files%5B%5D=ui.selectable.js&files%5B%5D=ui.sortable.js&files%5B%5D=ui.accordion.js&files%5B%5D=ui.autocomplete.js&files%5B%5D=ui.button.js&files%5B%5D=ui.dialog.js&files%5B%5D=ui.slider.js&files%5B%5D=ui.tabs.js&files%5B%5D=ui.datepicker.js&files%5B%5D=ui.progressbar.js&files%5B%5D=effects.core.js&files%5B%5D=effects.blind.js&files%5B%5D=effects.bounce.js&files%5B%5D=effects.clip.js&files%5B%5D=effects.drop.js&files%5B%5D=effects.explode.js&files%5B%5D=effects.fold.js&files%5B%5D=effects.highlight.js&files%5B%5D=effects.pulsate.js&files%5B%5D=effects.scale.js&files%5B%5D=effects.shake.js&files%5B%5D=effects.slide.js&files%5B%5D=effects.transfer.js&theme=%3FffDefault%3DLucida%2BGrande%2C%2BLucida%2BSans%2C%2BArial%2C%2Bsans-serif%26fwDefault%3Dbold%26fsDefault%3D1.1em%26cornerRadius%3D5px%26bgColorHeader%3D5c9ccc%26bgTextureHeader%3D12_gloss_wave.png%26bgImgOpacityHeader%3D55%26borderColorHeader%3D4297d7%26fcHeader%3Dffffff%26iconColorHeader%3Dd8e7f3%26bgColorContent%3Dfcfdfd%26bgTextureContent%3D06_inset_hard.png%26bgImgOpacityContent%3D100%26borderColorContent%3Da6c9e2%26fcContent%3D222222%26iconColorContent%3D469bdd%26bgColorDefault%3Ddfeffc%26bgTextureDefault%3D02_glass.png%26bgImgOpacityDefault%3D85%26borderColorDefault%3Dc5dbec%26fcDefault%3D2e6e9e%26iconColorDefault%3D6da8d5%26bgColorHover%3Dd0e5f5%26bgTextureHover%3D02_glass.png%26bgImgOpacityHover%3D75%26borderColorHover%3D79b7e7%26fcHover%3D1d5987%26iconColorHover%3D217bc0%26bgColorActive%3Df5f8f9%26bgTextureActive%3D06_inset_hard.png%26bgImgOpacityActive%3D100%26borderColorActive%3D79b7e7%26fcActive%3De17009%26iconColorActive%3Df9bd01%26bgColorHighlight%3Dfbec88%26bgTextureHighlight%3D01_flat.png%26bgImgOpacityHighlight%3D55%26borderColorHighlight%3Dfad42e%26fcHighlight%3D363636%26iconColorHighlight%3D2e83ff%26bgColorError%3Dfef1ec%26bgTextureError%3D02_glass.png%26bgImgOpacityError%3D95%26borderColorError%3Dcd0a0a%26fcError%3Dcd0a0a%26iconColorError%3Dcd0a0a%26bgColorOverlay%3Daaaaaa%26bgTextureOverlay%3D01_flat.png%26bgImgOpacityOverlay%3D0%26opacityOverlay%3D30%26bgColorShadow%3Daaaaaa%26bgTextureShadow%3D01_flat.png%26bgImgOpacityShadow%3D0%26opacityShadow%3D30%26thicknessShadow%3D8px%26offsetTopShadow%3D-8px%26offsetLeftShadow%3D-8px%26cornerRadiusShadow%3D8px&scope=&t-name=redmond&ui-version=1.8.2,download=true&files%5B%5D=ui.core.js&files%5B%5D=ui.widget.js&files%5B%5D=ui.mouse.js&files%5B%5D=ui.position.js&files%5B%5D=ui.draggable.js&files%5B%5D=ui.droppable.js&files%5B%5D=ui.resizable.js&files%5B%5D=ui.selectable.js&files%5B%5D=ui.sortable.js&files%5B%5D=ui.accordion.js&files%5B%5D=ui.autocomplete.js&files%5B%5D=ui.button.js&files%5B%5D=ui.dialog.js&files%5B%5D=ui.slider.js&files%5B%5D=ui.tabs.js&files%5B%5D=ui.datepicker.js&files%5B%5D=ui.progressbar.js&files%5B%5D=effects.core.js&files%5B%5D=effects.blind.js&files%5B%5D=effects.bounce.js&files%5B%5D=effects.clip.js&files%5B%5D=effects.drop.js&files%5B%5D=effects.explode.js&files%5B%5D=effects.fold.js&files%5B%5D=effects.highlight.js&files%5B%5D=effects.pulsate.js&files%5B%5D=effects.scale.js&files%5B%5D=effects.shake.js&files%5B%5D=effects.slide.js&files%5B%5D=effects.transfer.js&theme=%3FffDefault%3DSegoe%2BUI%252C%2BArial%252C%2Bsans-serif%26fwDefault%3Dbold%26fsDefault%3D1.1em%26cornerRadius%3D8px%26bgColorHeader%3D817865%26bgTextureHeader%3D12_gloss_wave.png%26bgImgOpacityHeader%3D45%26borderColorHeader%3D494437%26fcHeader%3Dffffff%26iconColorHeader%3Dfadc7a%26bgColorContent%3Dfeeebd%26bgTextureContent%3D03_highlight_soft.png%26bgImgOpacityContent%3D100%26borderColorContent%3D8e846b%26fcContent%3D383838%26iconColorContent%3Dd19405%26bgColorDefault%3Dfece2f%26bgTextureDefault%3D12_gloss_wave.png%26bgImgOpacityDefault%3D60%26borderColorDefault%3Dd19405%26fcDefault%3D4c3000%26iconColorDefault%3D3d3d3d%26bgColorHover%3Dffdd57%26bgTextureHover%3D12_gloss_wave.png%26bgImgOpacityHover%3D70%26borderColorHover%3Da45b13%26fcHover%3D381f00%26iconColorHover%3Dbd7b00%26bgColorActive%3Dffffff%26bgTextureActive%3D05_inset_soft.png%26bgImgOpacityActive%3D30%26borderColorActive%3D655e4e%26fcActive%3D0074c7%26iconColorActive%3Deb990f%26bgColorHighlight%3Dfff9e5%26bgTextureHighlight%3D12_gloss_wave.png%26bgImgOpacityHighlight%3D90%26borderColorHighlight%3Deeb420%26fcHighlight%3D1f1f1f%26iconColorHighlight%3Ded9f26%26bgColorError%3Dd34d17%26bgTextureError%3D07_diagonals_medium.png%26bgImgOpacityError%3D20%26borderColorError%3Dffb73d%26fcError%3Dffffff%26iconColorError%3Dffe180%26bgColorOverlay%3D5c5c5c%26bgTextureOverlay%3D01_flat.png%26bgImgOpacityOverlay%3D50%26opacityOverlay%3D80%26bgColorShadow%3Dcccccc%26bgTextureShadow%3D01_flat.png%26bgImgOpacityShadow%3D30%26opacityShadow%3D60%26thicknessShadow%3D7px%26offsetTopShadow%3D-7px%26offsetLeftShadow%3D-7px%26cornerRadiusShadow%3D8px&scope=&t-name=sunny&ui-version=1.8.2,download=true&files%5B%5D=ui.core.js&files%5B%5D=ui.widget.js&files%5B%5D=ui.mouse.js&files%5B%5D=ui.position.js&files%5B%5D=ui.draggable.js&files%5B%5D=ui.droppable.js&files%5B%5D=ui.resizable.js&files%5B%5D=ui.selectable.js&files%5B%5D=ui.sortable.js&files%5B%5D=ui.accordion.js&files%5B%5D=ui.autocomplete.js&files%5B%5D=ui.button.js&files%5B%5D=ui.dialog.js&files%5B%5D=ui.slider.js&files%5B%5D=ui.tabs.js&files%5B%5D=ui.datepicker.js&files%5B%5D=ui.progressbar.js&files%5B%5D=effects.core.js&files%5B%5D=effects.blind.js&files%5B%5D=effects.bounce.js&files%5B%5D=effects.clip.js&files%5B%5D=effects.drop.js&files%5B%5D=effects.explode.js&files%5B%5D=effects.fold.js&files%5B%5D=effects.highlight.js&files%5B%5D=effects.pulsate.js&files%5B%5D=effects.scale.js&files%5B%5D=effects.shake.js&files%5B%5D=effects.slide.js&files%5B%5D=effects.transfer.js&theme=%3FffDefault%3DTrebuchet%2BMS%252C%2BHelvetica%252C%2BArial%252C%2Bsans-serif%26fwDefault%3Dbold%26fsDefault%3D1.1em%26cornerRadius%3D6px%26bgColorHeader%3Ddddddd%26bgTextureHeader%3D02_glass.png%26bgImgOpacityHeader%3D35%26borderColorHeader%3Dbbbbbb%26fcHeader%3D444444%26iconColorHeader%3D999999%26bgColorContent%3Dc9c9c9%26bgTextureContent%3D05_inset_soft.png%26bgImgOpacityContent%3D50%26borderColorContent%3Daaaaaa%26fcContent%3D333333%26iconColorContent%3D999999%26bgColorDefault%3Deeeeee%26bgTextureDefault%3D02_glass.png%26bgImgOpacityDefault%3D60%26borderColorDefault%3Dcccccc%26fcDefault%3D3383bb%26iconColorDefault%3D70b2e1%26bgColorHover%3Df8f8f8%26bgTextureHover%3D02_glass.png%26bgImgOpacityHover%3D100%26borderColorHover%3Dbbbbbb%26fcHover%3D599fcf%26iconColorHover%3D3383bb%26bgColorActive%3D999999%26bgTextureActive%3D06_inset_hard.png%26bgImgOpacityActive%3D75%26borderColorActive%3D999999%26fcActive%3Dffffff%26iconColorActive%3D454545%26bgColorHighlight%3Deeeeee%26bgTextureHighlight%3D01_flat.png%26bgImgOpacityHighlight%3D55%26borderColorHighlight%3Dffffff%26fcHighlight%3D444444%26iconColorHighlight%3D3383bb%26bgColorError%3Dc0402a%26bgTextureError%3D01_flat.png%26bgImgOpacityError%3D55%26borderColorError%3Dc0402a%26fcError%3Dffffff%26iconColorError%3Dfbc856%26bgColorOverlay%3Deeeeee%26bgTextureOverlay%3D01_flat.png%26bgImgOpacityOverlay%3D0%26opacityOverlay%3D80%26bgColorShadow%3Daaaaaa%26bgTextureShadow%3D01_flat.png%26bgImgOpacityShadow%3D0%26opacityShadow%3D60%26thicknessShadow%3D4px%26offsetTopShadow%3D-4px%26offsetLeftShadow%3D-4px%26cornerRadiusShadow%3D0pxdow%253D0px&scope=&t-name=overcast&ui-version=1.8.2,download=true&files%5B%5D=ui.core.js&files%5B%5D=ui.widget.js&files%5B%5D=ui.mouse.js&files%5B%5D=ui.position.js&files%5B%5D=ui.draggable.js&files%5B%5D=ui.droppable.js&files%5B%5D=ui.resizable.js&files%5B%5D=ui.selectable.js&files%5B%5D=ui.sortable.js&files%5B%5D=ui.accordion.js&files%5B%5D=ui.autocomplete.js&files%5B%5D=ui.button.js&files%5B%5D=ui.dialog.js&files%5B%5D=ui.slider.js&files%5B%5D=ui.tabs.js&files%5B%5D=ui.datepicker.js&files%5B%5D=ui.progressbar.js&files%5B%5D=effects.core.js&files%5B%5D=effects.blind.js&files%5B%5D=effects.bounce.js&files%5B%5D=effects.clip.js&files%5B%5D=effects.drop.js&files%5B%5D=effects.explode.js&files%5B%5D=effects.fold.js&files%5B%5D=effects.highlight.js&files%5B%5D=effects.pulsate.js&files%5B%5D=effects.scale.js&files%5B%5D=effects.shake.js&files%5B%5D=effects.slide.js&files%5B%5D=effects.transfer.js&theme=%3FffDefault%3DLucida%2BGrande%252C%2BLucida%2BSans%252C%2BArial%252C%2Bsans-serif%26fwDefault%3Dnormal%26fsDefault%3D1.1em%26cornerRadius%3D10px%26bgColorHeader%3D3a8104%26bgTextureHeader%3D03_highlight_soft.png%26bgImgOpacityHeader%3D33%26borderColorHeader%3D3f7506%26fcHeader%3Dffffff%26iconColorHeader%3Dffffff%26bgColorContent%3D285c00%26bgTextureContent%3D05_inset_soft.png%26bgImgOpacityContent%3D10%26borderColorContent%3D72b42d%26fcContent%3Dffffff%26iconColorContent%3D72b42d%26bgColorDefault%3D4ca20b%26bgTextureDefault%3D03_highlight_soft.png%26bgImgOpacityDefault%3D60%26borderColorDefault%3D45930b%26fcDefault%3Dffffff%26iconColorDefault%3Dffffff%26bgColorHover%3D4eb305%26bgTextureHover%3D03_highlight_soft.png%26bgImgOpacityHover%3D50%26borderColorHover%3D8bd83b%26fcHover%3Dffffff%26iconColorHover%3Dffffff%26bgColorActive%3D285c00%26bgTextureActive%3D04_highlight_hard.png%26bgImgOpacityActive%3D30%26borderColorActive%3D72b42d%26fcActive%3Dffffff%26iconColorActive%3Dffffff%26bgColorHighlight%3Dfbf5d0%26bgTextureHighlight%3D02_glass.png%26bgImgOpacityHighlight%3D55%26borderColorHighlight%3Df9dd34%26fcHighlight%3D363636%26iconColorHighlight%3D4eb305%26bgColorError%3Dffdc2e%26bgTextureError%3D08_diagonals_thick.png%26bgImgOpacityError%3D95%26borderColorError%3Dfad000%26fcError%3D2b2b2b%26iconColorError%3Dcd0a0a%26bgColorOverlay%3D444444%26bgTextureOverlay%3D08_diagonals_thick.png%26bgImgOpacityOverlay%3D15%26opacityOverlay%3D30%26bgColorShadow%3Daaaaaa%26bgTextureShadow%3D07_diagonals_small.png%26bgImgOpacityShadow%3D0%26opacityShadow%3D30%26thicknessShadow%3D0px%26offsetTopShadow%3D4px%26offsetLeftShadow%3D4px%26cornerRadiusShadow%3D4px&scope=&t-name=le-frog&ui-version=1.8.2,download=true&files%5B%5D=ui.core.js&files%5B%5D=ui.widget.js&files%5B%5D=ui.mouse.js&files%5B%5D=ui.position.js&files%5B%5D=ui.draggable.js&files%5B%5D=ui.droppable.js&files%5B%5D=ui.resizable.js&files%5B%5D=ui.selectable.js&files%5B%5D=ui.sortable.js&files%5B%5D=ui.accordion.js&files%5B%5D=ui.autocomplete.js&files%5B%5D=ui.button.js&files%5B%5D=ui.dialog.js&files%5B%5D=ui.slider.js&files%5B%5D=ui.tabs.js&files%5B%5D=ui.datepicker.js&files%5B%5D=ui.progressbar.js&files%5B%5D=effects.core.js&files%5B%5D=effects.blind.js&files%5B%5D=effects.bounce.js&files%5B%5D=effects.clip.js&files%5B%5D=effects.drop.js&files%5B%5D=effects.explode.js&files%5B%5D=effects.fold.js&files%5B%5D=effects.highlight.js&files%5B%5D=effects.pulsate.js&files%5B%5D=effects.scale.js&files%5B%5D=effects.shake.js&files%5B%5D=effects.slide.js&files%5B%5D=effects.transfer.js&theme=%3FffDefault%3DHelvetica%252C%2BArial%252C%2Bsans-serif%26fwDefault%3Dbold%26fsDefault%3D1.1em%26cornerRadius%3D2px%26bgColorHeader%3Ddddddd%26bgTextureHeader%3D03_highlight_soft.png%26bgImgOpacityHeader%3D50%26borderColorHeader%3Ddddddd%26fcHeader%3D444444%26iconColorHeader%3D0073ea%26bgColorContent%3Dffffff%26bgTextureContent%3D01_flat.png%26bgImgOpacityContent%3D75%26borderColorContent%3Ddddddd%26fcContent%3D444444%26iconColorContent%3Dff0084%26bgColorDefault%3Df6f6f6%26bgTextureDefault%3D03_highlight_soft.png%26bgImgOpacityDefault%3D100%26borderColorDefault%3Ddddddd%26fcDefault%3D0073ea%26iconColorDefault%3D666666%26bgColorHover%3D0073ea%26bgTextureHover%3D03_highlight_soft.png%26bgImgOpacityHover%3D25%26borderColorHover%3D0073ea%26fcHover%3Dffffff%26iconColorHover%3Dffffff%26bgColorActive%3Dffffff%26bgTextureActive%3D02_glass.png%26bgImgOpacityActive%3D65%26borderColorActive%3Ddddddd%26fcActive%3Dff0084%26iconColorActive%3D454545%26bgColorHighlight%3Dffffff%26bgTextureHighlight%3D01_flat.png%26bgImgOpacityHighlight%3D55%26borderColorHighlight%3Dcccccc%26fcHighlight%3D444444%26iconColorHighlight%3D0073ea%26bgColorError%3Dffffff%26bgTextureError%3D01_flat.png%26bgImgOpacityError%3D55%26borderColorError%3Dff0084%26fcError%3D222222%26iconColorError%3Dff0084%26bgColorOverlay%3Deeeeee%26bgTextureOverlay%3D01_flat.png%26bgImgOpacityOverlay%3D0%26opacityOverlay%3D80%26bgColorShadow%3Daaaaaa%26bgTextureShadow%3D01_flat.png%26bgImgOpacityShadow%3D0%26opacityShadow%3D60%26thicknessShadow%3D4px%26offsetTopShadow%3D-4px%26offsetLeftShadow%3D-4px%26cornerRadiusShadow%3D0px&scope=&t-name=flick&ui-version=1.8.2,download=true&files%5B%5D=ui.core.js&files%5B%5D=ui.widget.js&files%5B%5D=ui.mouse.js&files%5B%5D=ui.position.js&files%5B%5D=ui.draggable.js&files%5B%5D=ui.droppable.js&files%5B%5D=ui.resizable.js&files%5B%5D=ui.selectable.js&files%5B%5D=ui.sortable.js&files%5B%5D=ui.accordion.js&files%5B%5D=ui.autocomplete.js&files%5B%5D=ui.button.js&files%5B%5D=ui.dialog.js&files%5B%5D=ui.slider.js&files%5B%5D=ui.tabs.js&files%5B%5D=ui.datepicker.js&files%5B%5D=ui.progressbar.js&files%5B%5D=effects.core.js&files%5B%5D=effects.blind.js&files%5B%5D=effects.bounce.js&files%5B%5D=effects.clip.js&files%5B%5D=effects.drop.js&files%5B%5D=effects.explode.js&files%5B%5D=effects.fold.js&files%5B%5D=effects.highlight.js&files%5B%5D=effects.pulsate.js&files%5B%5D=effects.scale.js&files%5B%5D=effects.shake.js&files%5B%5D=effects.slide.js&files%5B%5D=effects.transfer.js&theme=%3FffDefault%3DTrebuchet%2BMS%252C%2BTahoma%252C%2BVerdana%252C%2BArial%252C%2Bsans-serif%26fwDefault%3Dbold%26fsDefault%3D1.1em%26cornerRadius%3D6px%26bgColorHeader%3Dffffff%26bgTextureHeader%3D23_fine_grain.png%26bgImgOpacityHeader%3D15%26borderColorHeader%3Dd4d1bf%26fcHeader%3D453821%26iconColorHeader%3Db83400%26bgColorContent%3Deceadf%26bgTextureContent%3D23_fine_grain.png%26bgImgOpacityContent%3D10%26borderColorContent%3Dd9d6c4%26fcContent%3D1f1f1f%26iconColorContent%3D222222%26bgColorDefault%3Df8f7f6%26bgTextureDefault%3D23_fine_grain.png%26bgImgOpacityDefault%3D10%26borderColorDefault%3Dcbc7bd%26fcDefault%3D654b24%26iconColorDefault%3Db83400%26bgColorHover%3D654b24%26bgTextureHover%3D23_fine_grain.png%26bgImgOpacityHover%3D65%26borderColorHover%3D654b24%26fcHover%3Dffffff%26iconColorHover%3Dffffff%26bgColorActive%3Deceadf%26bgTextureActive%3D23_fine_grain.png%26bgImgOpacityActive%3D15%26borderColorActive%3Dd9d6c4%26fcActive%3D140f06%26iconColorActive%3D8c291d%26bgColorHighlight%3Df7f3de%26bgTextureHighlight%3D23_fine_grain.png%26bgImgOpacityHighlight%3D15%26borderColorHighlight%3Db2a266%26fcHighlight%3D3a3427%26iconColorHighlight%3D3572ac%26bgColorError%3Db83400%26bgTextureError%3D23_fine_grain.png%26bgImgOpacityError%3D68%26borderColorError%3D681818%26fcError%3Dffffff%26iconColorError%3Dfbdb93%26bgColorOverlay%3D6e4f1c%26bgTextureOverlay%3D16_diagonal_maze.png%26bgImgOpacityOverlay%3D20%26opacityOverlay%3D60%26bgColorShadow%3D000000%26bgTextureShadow%3D16_diagonal_maze.png%26bgImgOpacityShadow%3D40%26opacityShadow%3D60%26thicknessShadow%3D5px%26offsetTopShadow%3D0%26offsetLeftShadow%3D-10px%26cornerRadiusShadow%3D18px&scope=&t-name=pepper-grinder&ui-version=1.8.2,download=true&files%5B%5D=ui.core.js&files%5B%5D=ui.widget.js&files%5B%5D=ui.mouse.js&files%5B%5D=ui.position.js&files%5B%5D=ui.draggable.js&files%5B%5D=ui.droppable.js&files%5B%5D=ui.resizable.js&files%5B%5D=ui.selectable.js&files%5B%5D=ui.sortable.js&files%5B%5D=ui.accordion.js&files%5B%5D=ui.autocomplete.js&files%5B%5D=ui.button.js&files%5B%5D=ui.dialog.js&files%5B%5D=ui.slider.js&files%5B%5D=ui.tabs.js&files%5B%5D=ui.datepicker.js&files%5B%5D=ui.progressbar.js&files%5B%5D=effects.core.js&files%5B%5D=effects.blind.js&files%5B%5D=effects.bounce.js&files%5B%5D=effects.clip.js&files%5B%5D=effects.drop.js&files%5B%5D=effects.explode.js&files%5B%5D=effects.fold.js&files%5B%5D=effects.highlight.js&files%5B%5D=effects.pulsate.js&files%5B%5D=effects.scale.js&files%5B%5D=effects.shake.js&files%5B%5D=effects.slide.js&files%5B%5D=effects.transfer.js&theme=%3FffDefault%3DLucida%2BGrande%252C%2BLucida%2BSans%252C%2BArial%252C%2Bsans-serif%26fwDefault%3Dbold%26fsDefault%3D1.1em%26cornerRadius%3D6px%26bgColorHeader%3D30273a%26bgTextureHeader%3D03_highlight_soft.png%26bgImgOpacityHeader%3D25%26borderColorHeader%3D231d2b%26fcHeader%3Dffffff%26iconColorHeader%3Da8a3ae%26bgColorContent%3D3d3644%26bgTextureContent%3D12_gloss_wave.png%26bgImgOpacityContent%3D30%26borderColorContent%3D7e7783%26fcContent%3Dffffff%26iconColorContent%3Dffffff%26bgColorDefault%3Ddcd9de%26bgTextureDefault%3D03_highlight_soft.png%26bgImgOpacityDefault%3D100%26borderColorDefault%3Ddcd9de%26fcDefault%3D665874%26iconColorDefault%3D8d78a5%26bgColorHover%3Deae6ea%26bgTextureHover%3D03_highlight_soft.png%26bgImgOpacityHover%3D100%26borderColorHover%3Dd1c5d8%26fcHover%3D734d99%26iconColorHover%3D734d99%26bgColorActive%3D5f5964%26bgTextureActive%3D03_highlight_soft.png%26bgImgOpacityActive%3D45%26borderColorActive%3D7e7783%26fcActive%3Dffffff%26iconColorActive%3D454545%26bgColorHighlight%3Dfafafa%26bgTextureHighlight%3D01_flat.png%26bgImgOpacityHighlight%3D55%26borderColorHighlight%3Dffdb1f%26fcHighlight%3D333333%26iconColorHighlight%3D8d78a5%26bgColorError%3D994d53%26bgTextureError%3D01_flat.png%26bgImgOpacityError%3D55%26borderColorError%3D994d53%26fcError%3Dffffff%26iconColorError%3Debccce%26bgColorOverlay%3Deeeeee%26bgTextureOverlay%3D01_flat.png%26bgImgOpacityOverlay%3D0%26opacityOverlay%3D80%26bgColorShadow%3Daaaaaa%26bgTextureShadow%3D01_flat.png%26bgImgOpacityShadow%3D0%26opacityShadow%3D60%26thicknessShadow%3D4px%26offsetTopShadow%3D-4px%26offsetLeftShadow%3D-4px%26cornerRadiusShadow%3D0px&scope=&t-name=eggplant&ui-version=1.8.2,download=true&files%5B%5D=ui.core.js&files%5B%5D=ui.widget.js&files%5B%5D=ui.mouse.js&files%5B%5D=ui.position.js&files%5B%5D=ui.draggable.js&files%5B%5D=ui.droppable.js&files%5B%5D=ui.resizable.js&files%5B%5D=ui.selectable.js&files%5B%5D=ui.sortable.js&files%5B%5D=ui.accordion.js&files%5B%5D=ui.autocomplete.js&files%5B%5D=ui.button.js&files%5B%5D=ui.dialog.js&files%5B%5D=ui.slider.js&files%5B%5D=ui.tabs.js&files%5B%5D=ui.datepicker.js&files%5B%5D=ui.progressbar.js&files%5B%5D=effects.core.js&files%5B%5D=effects.blind.js&files%5B%5D=effects.bounce.js&files%5B%5D=effects.clip.js&files%5B%5D=effects.drop.js&files%5B%5D=effects.explode.js&files%5B%5D=effects.fold.js&files%5B%5D=effects.highlight.js&files%5B%5D=effects.pulsate.js&files%5B%5D=effects.scale.js&files%5B%5D=effects.shake.js&files%5B%5D=effects.slide.js&files%5B%5D=effects.transfer.js&theme=%3FffDefault%3DVerdana%252C%2BArial%252C%2Bsans-serif%26fwDefault%3Dnormal%26fsDefault%3D1.1em%26cornerRadius%3D6px%26bgColorHeader%3D444444%26bgTextureHeader%3D03_highlight_soft.png%26bgImgOpacityHeader%3D44%26borderColorHeader%3D333333%26fcHeader%3Dffffff%26iconColorHeader%3Dffffff%26bgColorContent%3D000000%26bgTextureContent%3D14_loop.png%26bgImgOpacityContent%3D25%26borderColorContent%3D555555%26fcContent%3Dffffff%26iconColorContent%3Dcccccc%26bgColorDefault%3D222222%26bgTextureDefault%3D03_highlight_soft.png%26bgImgOpacityDefault%3D35%26borderColorDefault%3D444444%26fcDefault%3Deeeeee%26iconColorDefault%3Dcccccc%26bgColorHover%3D003147%26bgTextureHover%3D03_highlight_soft.png%26bgImgOpacityHover%3D33%26borderColorHover%3D0b93d5%26fcHover%3Dffffff%26iconColorHover%3Dffffff%26bgColorActive%3D0972a5%26bgTextureActive%3D04_highlight_hard.png%26bgImgOpacityActive%3D20%26borderColorActive%3D26b3f7%26fcActive%3Dffffff%26iconColorActive%3D222222%26bgColorHighlight%3Deeeeee%26bgTextureHighlight%3D03_highlight_soft.png%26bgImgOpacityHighlight%3D80%26borderColorHighlight%3Dcccccc%26fcHighlight%3D2e7db2%26iconColorHighlight%3D4b8e0b%26bgColorError%3Dffc73d%26bgTextureError%3D02_glass.png%26bgImgOpacityError%3D40%26borderColorError%3Dffb73d%26fcError%3D111111%26iconColorError%3Da83300%26bgColorOverlay%3D5c5c5c%26bgTextureOverlay%3D01_flat.png%26bgImgOpacityOverlay%3D50%26opacityOverlay%3D80%26bgColorShadow%3Dcccccc%26bgTextureShadow%3D01_flat.png%26bgImgOpacityShadow%3D30%26opacityShadow%3D60%26thicknessShadow%3D7px%26offsetTopShadow%3D-7px%26offsetLeftShadow%3D-7px%26cornerRadiusShadow%3D8px&scope=&t-name=dark-hive&ui-version=1.8.2,download=true&files%5B%5D=ui.core.js&files%5B%5D=ui.widget.js&files%5B%5D=ui.mouse.js&files%5B%5D=ui.position.js&files%5B%5D=ui.draggable.js&files%5B%5D=ui.droppable.js&files%5B%5D=ui.resizable.js&files%5B%5D=ui.selectable.js&files%5B%5D=ui.sortable.js&files%5B%5D=ui.accordion.js&files%5B%5D=ui.autocomplete.js&files%5B%5D=ui.button.js&files%5B%5D=ui.dialog.js&files%5B%5D=ui.slider.js&files%5B%5D=ui.tabs.js&files%5B%5D=ui.datepicker.js&files%5B%5D=ui.progressbar.js&files%5B%5D=effects.core.js&files%5B%5D=effects.blind.js&files%5B%5D=effects.bounce.js&files%5B%5D=effects.clip.js&files%5B%5D=effects.drop.js&files%5B%5D=effects.explode.js&files%5B%5D=effects.fold.js&files%5B%5D=effects.highlight.js&files%5B%5D=effects.pulsate.js&files%5B%5D=effects.scale.js&files%5B%5D=effects.shake.js&files%5B%5D=effects.slide.js&files%5B%5D=effects.transfer.js&theme=%3FffDefault%3DLucida%2BGrande%252C%2BLucida%2BSans%252C%2BArial%252C%2Bsans-serif%26fwDefault%3Dbold%26fsDefault%3D1.1em%26cornerRadius%3D6px%26bgColorHeader%3Ddeedf7%26bgTextureHeader%3D03_highlight_soft.png%26bgImgOpacityHeader%3D100%26borderColorHeader%3Daed0ea%26fcHeader%3D222222%26iconColorHeader%3D72a7cf%26bgColorContent%3Df2f5f7%26bgTextureContent%3D04_highlight_hard.png%26bgImgOpacityContent%3D100%26borderColorContent%3Ddddddd%26fcContent%3D362b36%26iconColorContent%3D72a7cf%26bgColorDefault%3Dd7ebf9%26bgTextureDefault%3D02_glass.png%26bgImgOpacityDefault%3D80%26borderColorDefault%3Daed0ea%26fcDefault%3D2779aa%26iconColorDefault%3D3d80b3%26bgColorHover%3De4f1fb%26bgTextureHover%3D02_glass.png%26bgImgOpacityHover%3D100%26borderColorHover%3D74b2e2%26fcHover%3D0070a3%26iconColorHover%3D2694e8%26bgColorActive%3D3baae3%26bgTextureActive%3D02_glass.png%26bgImgOpacityActive%3D50%26borderColorActive%3D2694e8%26fcActive%3Dffffff%26iconColorActive%3Dffffff%26bgColorHighlight%3Dffef8f%26bgTextureHighlight%3D03_highlight_soft.png%26bgImgOpacityHighlight%3D25%26borderColorHighlight%3Df9dd34%26fcHighlight%3D363636%26iconColorHighlight%3D2e83ff%26bgColorError%3Dcd0a0a%26bgTextureError%3D01_flat.png%26bgImgOpacityError%3D15%26borderColorError%3Dcd0a0a%26fcError%3Dffffff%26iconColorError%3Dffffff%26bgColorOverlay%3Deeeeee%26bgTextureOverlay%3D08_diagonals_thick.png%26bgImgOpacityOverlay%3D90%26opacityOverlay%3D80%26bgColorShadow%3D000000%26bgTextureShadow%3D04_highlight_hard.png%26bgImgOpacityShadow%3D70%26opacityShadow%3D30%26thicknessShadow%3D7px%26offsetTopShadow%3D-7px%26offsetLeftShadow%3D-7px%26cornerRadiusShadow%3D8px&scope=&t-name=cupertino&ui-version=1.8.2,download=true&files%5B%5D=ui.core.js&files%5B%5D=ui.widget.js&files%5B%5D=ui.mouse.js&files%5B%5D=ui.position.js&files%5B%5D=ui.draggable.js&files%5B%5D=ui.droppable.js&files%5B%5D=ui.resizable.js&files%5B%5D=ui.selectable.js&files%5B%5D=ui.sortable.js&files%5B%5D=ui.accordion.js&files%5B%5D=ui.autocomplete.js&files%5B%5D=ui.button.js&files%5B%5D=ui.dialog.js&files%5B%5D=ui.slider.js&files%5B%5D=ui.tabs.js&files%5B%5D=ui.datepicker.js&files%5B%5D=ui.progressbar.js&files%5B%5D=effects.core.js&files%5B%5D=effects.blind.js&files%5B%5D=effects.bounce.js&files%5B%5D=effects.clip.js&files%5B%5D=effects.drop.js&files%5B%5D=effects.explode.js&files%5B%5D=effects.fold.js&files%5B%5D=effects.highlight.js&files%5B%5D=effects.pulsate.js&files%5B%5D=effects.scale.js&files%5B%5D=effects.shake.js&files%5B%5D=effects.slide.js&files%5B%5D=effects.transfer.js&theme=%3FffDefault%3Dsegoe%2Bui%252C%2BArial%252C%2Bsans-serif%26fwDefault%3Dbold%26fsDefault%3D1.1em%26cornerRadius%3D6px%26bgColorHeader%3Dece8da%26bgTextureHeader%3D12_gloss_wave.png%26bgImgOpacityHeader%3D100%26borderColorHeader%3Dd4ccb0%26fcHeader%3D433f38%26iconColorHeader%3D847e71%26bgColorContent%3Df5f3e5%26bgTextureContent%3D04_highlight_hard.png%26bgImgOpacityContent%3D100%26borderColorContent%3Ddfd9c3%26fcContent%3D312e25%26iconColorContent%3D808080%26bgColorDefault%3D459e00%26bgTextureDefault%3D04_highlight_hard.png%26bgImgOpacityDefault%3D15%26borderColorDefault%3D327E04%26fcDefault%3Dffffff%26iconColorDefault%3Deeeeee%26bgColorHover%3D67b021%26bgTextureHover%3D03_highlight_soft.png%26bgImgOpacityHover%3D25%26borderColorHover%3D327E04%26fcHover%3Dffffff%26iconColorHover%3Dffffff%26bgColorActive%3Dfafaf4%26bgTextureActive%3D04_highlight_hard.png%26bgImgOpacityActive%3D100%26borderColorActive%3Dd4ccb0%26fcActive%3D459e00%26iconColorActive%3D8DC262%26bgColorHighlight%3Dfcf0ba%26bgTextureHighlight%3D02_glass.png%26bgImgOpacityHighlight%3D55%26borderColorHighlight%3De8e1b5%26fcHighlight%3D363636%26iconColorHighlight%3D8DC262%26bgColorError%3Dffedad%26bgTextureError%3D03_highlight_soft.png%26bgImgOpacityError%3D95%26borderColorError%3De3a345%26fcError%3Dcd5c0a%26iconColorError%3Dcd0a0a%26bgColorOverlay%3D2b2922%26bgTextureOverlay%3D05_inset_soft.png%26bgImgOpacityOverlay%3D15%26opacityOverlay%3D90%26bgColorShadow%3Dcccccc%26bgTextureShadow%3D04_highlight_hard.png%26bgImgOpacityShadow%3D95%26opacityShadow%3D20%26thicknessShadow%3D12px%26offsetTopShadow%3D-12px%26offsetLeftShadow%3D-12px%26cornerRadiusShadow%3D10px&scope=&t-name=south-street&ui-version=1.8.2,download=true&files%5B%5D=ui.core.js&files%5B%5D=ui.widget.js&files%5B%5D=ui.mouse.js&files%5B%5D=ui.position.js&files%5B%5D=ui.draggable.js&files%5B%5D=ui.droppable.js&files%5B%5D=ui.resizable.js&files%5B%5D=ui.selectable.js&files%5B%5D=ui.sortable.js&files%5B%5D=ui.accordion.js&files%5B%5D=ui.autocomplete.js&files%5B%5D=ui.button.js&files%5B%5D=ui.dialog.js&files%5B%5D=ui.slider.js&files%5B%5D=ui.tabs.js&files%5B%5D=ui.datepicker.js&files%5B%5D=ui.progressbar.js&files%5B%5D=effects.core.js&files%5B%5D=effects.blind.js&files%5B%5D=effects.bounce.js&files%5B%5D=effects.clip.js&files%5B%5D=effects.drop.js&files%5B%5D=effects.explode.js&files%5B%5D=effects.fold.js&files%5B%5D=effects.highlight.js&files%5B%5D=effects.pulsate.js&files%5B%5D=effects.scale.js&files%5B%5D=effects.shake.js&files%5B%5D=effects.slide.js&files%5B%5D=effects.transfer.js&theme=%3FffDefault%3DArial%2Csans-serif%26fwDefault%3Dbold%26fsDefault%3D1.1em%26cornerRadius%3D6px%26bgColorHeader%3Dcc0000%26bgTextureHeader%3D03_highlight_soft.png%26bgImgOpacityHeader%3D15%26borderColorHeader%3De3a1a1%26fcHeader%3Dffffff%26iconColorHeader%3Dffffff%26bgColorContent%3Dffffff%26bgTextureContent%3D01_flat.png%26bgImgOpacityContent%3D75%26borderColorContent%3Deeeeee%26fcContent%3D333333%26iconColorContent%3Dcc0000%26bgColorDefault%3Deeeeee%26bgTextureDefault%3D04_highlight_hard.png%26bgImgOpacityDefault%3D100%26borderColorDefault%3Dd8dcdf%26fcDefault%3D004276%26iconColorDefault%3Dcc0000%26bgColorHover%3Df6f6f6%26bgTextureHover%3D04_highlight_hard.png%26bgImgOpacityHover%3D100%26borderColorHover%3Dcdd5da%26fcHover%3D111111%26iconColorHover%3Dcc0000%26bgColorActive%3Dffffff%26bgTextureActive%3D01_flat.png%26bgImgOpacityActive%3D65%26borderColorActive%3Deeeeee%26fcActive%3Dcc0000%26iconColorActive%3Dcc0000%26bgColorHighlight%3Dfbf8ee%26bgTextureHighlight%3D02_glass.png%26bgImgOpacityHighlight%3D55%26borderColorHighlight%3Dfcd3a1%26fcHighlight%3D444444%26iconColorHighlight%3D004276%26bgColorError%3Df3d8d8%26bgTextureError%3D08_diagonals_thick.png%26bgImgOpacityError%3D75%26borderColorError%3Dcc0000%26fcError%3D2e2e2e%26iconColorError%3Dcc0000%26bgColorOverlay%3Da6a6a6%26bgTextureOverlay%3D09_dots_small.png%26bgImgOpacityOverlay%3D65%26opacityOverlay%3D40%26bgColorShadow%3D333333%26bgTextureShadow%3D01_flat.png%26bgImgOpacityShadow%3D0%26opacityShadow%3D10%26thicknessShadow%3D8px%26offsetTopShadow%3D-8px%26offsetLeftShadow%3D-8px%26cornerRadiusShadow%3D8px&scope=&t-name=blitzer&ui-version=1.8.2,download=true&files%5B%5D=ui.core.js&files%5B%5D=ui.widget.js&files%5B%5D=ui.mouse.js&files%5B%5D=ui.position.js&files%5B%5D=ui.draggable.js&files%5B%5D=ui.droppable.js&files%5B%5D=ui.resizable.js&files%5B%5D=ui.selectable.js&files%5B%5D=ui.sortable.js&files%5B%5D=ui.accordion.js&files%5B%5D=ui.autocomplete.js&files%5B%5D=ui.button.js&files%5B%5D=ui.dialog.js&files%5B%5D=ui.slider.js&files%5B%5D=ui.tabs.js&files%5B%5D=ui.datepicker.js&files%5B%5D=ui.progressbar.js&files%5B%5D=effects.core.js&files%5B%5D=effects.blind.js&files%5B%5D=effects.bounce.js&files%5B%5D=effects.clip.js&files%5B%5D=effects.drop.js&files%5B%5D=effects.explode.js&files%5B%5D=effects.fold.js&files%5B%5D=effects.highlight.js&files%5B%5D=effects.pulsate.js&files%5B%5D=effects.scale.js&files%5B%5D=effects.shake.js&files%5B%5D=effects.slide.js&files%5B%5D=effects.transfer.js&theme=%3Ftr%3DffDefault%3DHelvetica%2CArial%2Csans-serif%26fwDefault%3Dnormal%26fsDefault%3D1.1em%26cornerRadius%3D6px%26bgColorHeader%3Dcb842e%26bgTextureHeader%3D02_glass.png%26bgImgOpacityHeader%3D25%26borderColorHeader%3Dd49768%26fcHeader%3Dffffff%26iconColorHeader%3Dffffff%26bgColorContent%3Df4f0ec%26bgTextureContent%3D05_inset_soft.png%26bgImgOpacityContent%3D100%26borderColorContent%3De0cfc2%26fcContent%3D1e1b1d%26iconColorContent%3Dc47a23%26bgColorDefault%3Dede4d4%26bgTextureDefault%3D02_glass.png%26bgImgOpacityDefault%3D70%26borderColorDefault%3Dcdc3b7%26fcDefault%3D3f3731%26iconColorDefault%3Df08000%26bgColorHover%3Df5f0e5%26bgTextureHover%3D02_glass.png%26bgImgOpacityHover%3D100%26borderColorHover%3Df5ad66%26fcHover%3Da46313%26iconColorHover%3Df08000%26bgColorActive%3Df4f0ec%26bgTextureActive%3D04_highlight_hard.png%26bgImgOpacityActive%3D100%26borderColorActive%3De0cfc2%26fcActive%3Db85700%26iconColorActive%3Df35f07%26bgColorHighlight%3Df5f5b5%26bgTextureHighlight%3D04_highlight_hard.png%26bgImgOpacityHighlight%3D75%26borderColorHighlight%3Dd9bb73%26fcHighlight%3D060200%26iconColorHighlight%3Dcb672b%26bgColorError%3Dfee4bd%26bgTextureError%3D04_highlight_hard.png%26bgImgOpacityError%3D65%26borderColorError%3Df8893f%26fcError%3D592003%26iconColorError%3Dff7519%26bgColorOverlay%3Daaaaaa%26bgTextureOverlay%3D01_flat.png%26bgImgOpacityOverlay%3D75%26opacityOverlay%3D30%26bgColorShadow%3Daaaaaa%26bgTextureShadow%3D01_flat.png%26bgImgOpacityShadow%3D75%26opacityShadow%3D30%26thicknessShadow%3D8px%26offsetTopShadow%3D-8px%26offsetLeftShadow%3D-8px%26cornerRadiusShadow%3D8px&scope=&t-name=humanity&ui-version=1.8.2,download=true&files%5B%5D=ui.core.js&files%5B%5D=ui.widget.js&files%5B%5D=ui.mouse.js&files%5B%5D=ui.position.js&files%5B%5D=ui.draggable.js&files%5B%5D=ui.droppable.js&files%5B%5D=ui.resizable.js&files%5B%5D=ui.selectable.js&files%5B%5D=ui.sortable.js&files%5B%5D=ui.accordion.js&files%5B%5D=ui.autocomplete.js&files%5B%5D=ui.button.js&files%5B%5D=ui.dialog.js&files%5B%5D=ui.slider.js&files%5B%5D=ui.tabs.js&files%5B%5D=ui.datepicker.js&files%5B%5D=ui.progressbar.js&files%5B%5D=effects.core.js&files%5B%5D=effects.blind.js&files%5B%5D=effects.bounce.js&files%5B%5D=effects.clip.js&files%5B%5D=effects.drop.js&files%5B%5D=effects.explode.js&files%5B%5D=effects.fold.js&files%5B%5D=effects.highlight.js&files%5B%5D=effects.pulsate.js&files%5B%5D=effects.scale.js&files%5B%5D=effects.shake.js&files%5B%5D=effects.slide.js&files%5B%5D=effects.transfer.js&theme=%3FffDefault%3DGill%2BSans%2CArial%2Csans-serif%26fwDefault%3Dbold%26fsDefault%3D1.2em%26cornerRadius%3D4px%26bgColorHeader%3D35414f%26bgTextureHeader%3D09_dots_small.png%26bgImgOpacityHeader%3D35%26borderColorHeader%3D2c4359%26fcHeader%3De1e463%26iconColorHeader%3De1e463%26bgColorContent%3Dffffff%26bgTextureContent%3D01_flat.png%26bgImgOpacityContent%3D75%26borderColorContent%3Daaaaaa%26fcContent%3D2c4359%26iconColorContent%3Dc02669%26bgColorDefault%3D93c3cd%26bgTextureDefault%3D07_diagonals_small.png%26bgImgOpacityDefault%3D50%26borderColorDefault%3D93c3cd%26fcDefault%3D333333%26iconColorDefault%3Dffffff%26bgColorHover%3Dccd232%26bgTextureHover%3D07_diagonals_small.png%26bgImgOpacityHover%3D75%26borderColorHover%3D999999%26fcHover%3D212121%26iconColorHover%3D454545%26bgColorActive%3Ddb4865%26bgTextureActive%3D07_diagonals_small.png%26bgImgOpacityActive%3D40%26borderColorActive%3Dff6b7f%26fcActive%3Dffffff%26iconColorActive%3Dffffff%26bgColorHighlight%3Dffff38%26bgTextureHighlight%3D10_dots_medium.png%26bgImgOpacityHighlight%3D80%26borderColorHighlight%3Db4d100%26fcHighlight%3D363636%26iconColorHighlight%3D88a206%26bgColorError%3Dff3853%26bgTextureError%3D07_diagonals_small.png%26bgImgOpacityError%3D50%26borderColorError%3Dff6b7f%26fcError%3Dffffff%26iconColorError%3Dffeb33%26bgColorOverlay%3Df7f7ba%26bgTextureOverlay%3D11_white_lines.png%26bgImgOpacityOverlay%3D85%26opacityOverlay%3D80%26bgColorShadow%3Dba9217%26bgTextureShadow%3D01_flat.png%26bgImgOpacityShadow%3D75%26opacityShadow%3D20%26thicknessShadow%3D10px%26offsetTopShadow%3D8px%26offsetLeftShadow%3D8px%26cornerRadiusShadow%3D5px&scope=&t-name=hot-sneaks&ui-version=1.8.2,download=true&files%5B%5D=ui.core.js&files%5B%5D=ui.widget.js&files%5B%5D=ui.mouse.js&files%5B%5D=ui.position.js&files%5B%5D=ui.draggable.js&files%5B%5D=ui.droppable.js&files%5B%5D=ui.resizable.js&files%5B%5D=ui.selectable.js&files%5B%5D=ui.sortable.js&files%5B%5D=ui.accordion.js&files%5B%5D=ui.autocomplete.js&files%5B%5D=ui.button.js&files%5B%5D=ui.dialog.js&files%5B%5D=ui.slider.js&files%5B%5D=ui.tabs.js&files%5B%5D=ui.datepicker.js&files%5B%5D=ui.progressbar.js&files%5B%5D=effects.core.js&files%5B%5D=effects.blind.js&files%5B%5D=effects.bounce.js&files%5B%5D=effects.clip.js&files%5B%5D=effects.drop.js&files%5B%5D=effects.explode.js&files%5B%5D=effects.fold.js&files%5B%5D=effects.highlight.js&files%5B%5D=effects.pulsate.js&files%5B%5D=effects.scale.js&files%5B%5D=effects.shake.js&files%5B%5D=effects.slide.js&files%5B%5D=effects.transfer.js&theme=%3FffDefault%3Dsegoe%2Bui%2C%2BArial%2C%2Bsans-serif%26fwDefault%3Dbold%26fsDefault%3D1.1em%26cornerRadius%3D3px%26bgColorHeader%3Df9f9f9%26bgTextureHeader%3D03_highlight_soft.png%26bgImgOpacityHeader%3D100%26borderColorHeader%3Dcccccc%26fcHeader%3De69700%26iconColorHeader%3D5fa5e3%26bgColorContent%3Deeeeee%26bgTextureContent%3D06_inset_hard.png%26bgImgOpacityContent%3D100%26borderColorContent%3Daaaaaa%26fcContent%3D222222%26iconColorContent%3D0a82eb%26bgColorDefault%3D1484e6%26bgTextureDefault%3D08_diagonals_thick.png%26bgImgOpacityDefault%3D22%26borderColorDefault%3Dffffff%26fcDefault%3Dffffff%26iconColorDefault%3Dfcdd4a%26bgColorHover%3D2293f7%26bgTextureHover%3D08_diagonals_thick.png%26bgImgOpacityHover%3D26%26borderColorHover%3D2293f7%26fcHover%3Dffffff%26iconColorHover%3Dffffff%26bgColorActive%3De69700%26bgTextureActive%3D08_diagonals_thick.png%26bgImgOpacityActive%3D20%26borderColorActive%3De69700%26fcActive%3Dffffff%26iconColorActive%3Dffffff%26bgColorHighlight%3Dc5ddfc%26bgTextureHighlight%3D07_diagonals_small.png%26bgImgOpacityHighlight%3D25%26borderColorHighlight%3Dffffff%26fcHighlight%3D333333%26iconColorHighlight%3D0b54d5%26bgColorError%3De69700%26bgTextureError%3D08_diagonals_thick.png%26bgImgOpacityError%3D20%26borderColorError%3De69700%26fcError%3Dffffff%26iconColorError%3Dffffff%26bgColorOverlay%3De6b900%26bgTextureOverlay%3D01_flat.png%26bgImgOpacityOverlay%3D0%26opacityOverlay%3D30%26bgColorShadow%3De69700%26bgTextureShadow%3D01_flat.png%26bgImgOpacityShadow%3D0%26opacityShadow%3D20%26thicknessShadow%3D0px%26offsetTopShadow%3D6px%26offsetLeftShadow%3D6px%26cornerRadiusShadow%3D3px&scope=&t-name=excite-bike&ui-version=1.8.2,download=true&files%5B%5D=ui.core.js&files%5B%5D=ui.widget.js&files%5B%5D=ui.mouse.js&files%5B%5D=ui.position.js&files%5B%5D=ui.draggable.js&files%5B%5D=ui.droppable.js&files%5B%5D=ui.resizable.js&files%5B%5D=ui.selectable.js&files%5B%5D=ui.sortable.js&files%5B%5D=ui.accordion.js&files%5B%5D=ui.autocomplete.js&files%5B%5D=ui.button.js&files%5B%5D=ui.dialog.js&files%5B%5D=ui.slider.js&files%5B%5D=ui.tabs.js&files%5B%5D=ui.datepicker.js&files%5B%5D=ui.progressbar.js&files%5B%5D=effects.core.js&files%5B%5D=effects.blind.js&files%5B%5D=effects.bounce.js&files%5B%5D=effects.clip.js&files%5B%5D=effects.drop.js&files%5B%5D=effects.explode.js&files%5B%5D=effects.fold.js&files%5B%5D=effects.highlight.js&files%5B%5D=effects.pulsate.js&files%5B%5D=effects.scale.js&files%5B%5D=effects.shake.js&files%5B%5D=effects.slide.js&files%5B%5D=effects.transfer.js&theme=%3Ftr%26ffDefault%3DHelvetica%2C%2BArial%2C%2Bsans-serif%26fwDefault%3Dnormal%26fsDefault%3D1.1%26fsDefaultUnit%3Dem%26cornerRadius%3D5%26cornerRadiusUnit%3Dpx%26bgColorHeader%3D888888%26bgTextureHeader%3D04_highlight_hard.png%26bgImgOpacityHeader%3D15%26borderColorHeader%3D404040%26fcHeader%3Dffffff%26iconColorHeader%3Dcccccc%26bgColorContent%3D121212%26bgTextureContent%3D12_gloss_wave.png%26bgImgOpacityContent%3D16%26borderColorContent%3D404040%26fcContent%3Deeeeee%26iconColorContent%3Dbbbbbb%26bgColorDefault%3Dadadad%26bgTextureDefault%3D03_highlight_soft.png%26bgImgOpacityDefault%3D35%26borderColorDefault%3Dcccccc%26fcDefault%3D333333%26iconColorDefault%3D666666%26bgColorHover%3Ddddddd%26bgTextureHover%3D03_highlight_soft.png%26bgImgOpacityHover%3D60%26borderColorHover%3Ddddddd%26fcHover%3D000000%26iconColorHover%3Dc98000%26bgColorActive%3D121212%26bgTextureActive%3D05_inset_soft.png%26bgImgOpacityActive%3D15%26borderColorActive%3D000000%26fcActive%3Dffffff%26iconColorActive%3Df29a00%26bgColorHighlight%3D555555%26bgTextureHighlight%3D04_highlight_hard.png%26bgImgOpacityHighlight%3D55%26borderColorHighlight%3D404040%26fcHighlight%3Dcccccc%26iconColorHighlight%3Daaaaaa%26bgColorError%3Dfef1ec%26bgTextureError%3D02_glass.png%26bgImgOpacityError%3D95%26borderColorError%3Dcd0a0a%26fcError%3Dcd0a0a%26iconColorError%3Dcd0a0a&scope=&t-name=vader&ui-version=1.8.2,download=true&files%5B%5D=ui.core.js&files%5B%5D=ui.widget.js&files%5B%5D=ui.mouse.js&files%5B%5D=ui.position.js&files%5B%5D=ui.draggable.js&files%5B%5D=ui.droppable.js&files%5B%5D=ui.resizable.js&files%5B%5D=ui.selectable.js&files%5B%5D=ui.sortable.js&files%5B%5D=ui.accordion.js&files%5B%5D=ui.autocomplete.js&files%5B%5D=ui.button.js&files%5B%5D=ui.dialog.js&files%5B%5D=ui.slider.js&files%5B%5D=ui.tabs.js&files%5B%5D=ui.datepicker.js&files%5B%5D=ui.progressbar.js&files%5B%5D=effects.core.js&files%5B%5D=effects.blind.js&files%5B%5D=effects.bounce.js&files%5B%5D=effects.clip.js&files%5B%5D=effects.drop.js&files%5B%5D=effects.explode.js&files%5B%5D=effects.fold.js&files%5B%5D=effects.highlight.js&files%5B%5D=effects.pulsate.js&files%5B%5D=effects.scale.js&files%5B%5D=effects.shake.js&files%5B%5D=effects.slide.js&files%5B%5D=effects.transfer.js&theme=%3FffDefault%3DArial%2C%2Bsans-serif%26fwDefault%3Dbold%26fsDefault%3D1.3em%26cornerRadius%3D4px%26bgColorHeader%3D0b3e6f%26bgTextureHeader%3D08_diagonals_thick.png%26bgImgOpacityHeader%3D15%26borderColorHeader%3D0b3e6f%26fcHeader%3Df6f6f6%26iconColorHeader%3D98d2fb%26bgColorContent%3D111111%26bgTextureContent%3D12_gloss_wave.png%26bgImgOpacityContent%3D20%26borderColorContent%3D000000%26fcContent%3Dd9d9d9%26iconColorContent%3D9ccdfc%26bgColorDefault%3D333333%26bgTextureDefault%3D09_dots_small.png%26bgImgOpacityDefault%3D20%26borderColorDefault%3D333333%26fcDefault%3Dffffff%26iconColorDefault%3D9ccdfc%26bgColorHover%3D00498f%26bgTextureHover%3D09_dots_small.png%26bgImgOpacityHover%3D40%26borderColorHover%3D222222%26fcHover%3Dffffff%26iconColorHover%3Dffffff%26bgColorActive%3D292929%26bgTextureActive%3D01_flat.png%26bgImgOpacityActive%3D40%26borderColorActive%3D096ac8%26fcActive%3D75abff%26iconColorActive%3D00498f%26bgColorHighlight%3D0b58a2%26bgTextureHighlight%3D10_dots_medium.png%26bgImgOpacityHighlight%3D30%26borderColorHighlight%3D052f57%26fcHighlight%3Dffffff%26iconColorHighlight%3Dffffff%26bgColorError%3Da32d00%26bgTextureError%3D09_dots_small.png%26bgImgOpacityError%3D30%26borderColorError%3Dcd0a0a%26fcError%3Dffffff%26iconColorError%3Dffffff%26bgColorOverlay%3Daaaaaa%26bgTextureOverlay%3D01_flat.png%26bgImgOpacityOverlay%3D0%26opacityOverlay%3D30%26bgColorShadow%3Daaaaaa%26bgTextureShadow%3D01_flat.png%26bgImgOpacityShadow%3D0%26opacityShadow%3D30%26thicknessShadow%3D8px%26offsetTopShadow%3D-8px%26offsetLeftShadow%3D-8px%26cornerRadiusShadow%3D8px&scope=&t-name=dot-luv&ui-version=1.8.2,download=true&files%5B%5D=ui.core.js&files%5B%5D=ui.widget.js&files%5B%5D=ui.mouse.js&files%5B%5D=ui.position.js&files%5B%5D=ui.draggable.js&files%5B%5D=ui.droppable.js&files%5B%5D=ui.resizable.js&files%5B%5D=ui.selectable.js&files%5B%5D=ui.sortable.js&files%5B%5D=ui.accordion.js&files%5B%5D=ui.autocomplete.js&files%5B%5D=ui.button.js&files%5B%5D=ui.dialog.js&files%5B%5D=ui.slider.js&files%5B%5D=ui.tabs.js&files%5B%5D=ui.datepicker.js&files%5B%5D=ui.progressbar.js&files%5B%5D=effects.core.js&files%5B%5D=effects.blind.js&files%5B%5D=effects.bounce.js&files%5B%5D=effects.clip.js&files%5B%5D=effects.drop.js&files%5B%5D=effects.explode.js&files%5B%5D=effects.fold.js&files%5B%5D=effects.highlight.js&files%5B%5D=effects.pulsate.js&files%5B%5D=effects.scale.js&files%5B%5D=effects.shake.js&files%5B%5D=effects.slide.js&files%5B%5D=effects.transfer.js&theme=%3FffDefault%3DSegoe%2BUI%252C%2BHelvetica%252C%2BArial%252C%2Bsans-serif%26fwDefault%3Dbold%26fsDefault%3D1.1em%26cornerRadius%3D4px%26bgColorHeader%3D453326%26bgTextureHeader%3D12_gloss_wave.png%26bgImgOpacityHeader%3D25%26borderColorHeader%3D695649%26fcHeader%3De3ddc9%26iconColorHeader%3De3ddc9%26bgColorContent%3D201913%26bgTextureContent%3D05_inset_soft.png%26bgImgOpacityContent%3D10%26borderColorContent%3D9c947c%26fcContent%3Dffffff%26iconColorContent%3D222222%26bgColorDefault%3D1c160d%26bgTextureDefault%3D12_gloss_wave.png%26bgImgOpacityDefault%3D20%26borderColorDefault%3D695444%26fcDefault%3D9bcc60%26iconColorDefault%3D9bcc60%26bgColorHover%3D44372c%26bgTextureHover%3D12_gloss_wave.png%26bgImgOpacityHover%3D30%26borderColorHover%3D9c947c%26fcHover%3Dbaec7e%26iconColorHover%3Dadd978%26bgColorActive%3D201913%26bgTextureActive%3D03_highlight_soft.png%26bgImgOpacityActive%3D20%26borderColorActive%3D9c947c%26fcActive%3De3ddc9%26iconColorActive%3De3ddc9%26bgColorHighlight%3D619226%26bgTextureHighlight%3D03_highlight_soft.png%26bgImgOpacityHighlight%3D20%26borderColorHighlight%3Dadd978%26fcHighlight%3Dffffff%26iconColorHighlight%3Dffffff%26bgColorError%3D5f391b%26bgTextureError%3D02_glass.png%26bgImgOpacityError%3D15%26borderColorError%3D5f391b%26fcError%3Dffffff%26iconColorError%3Df1fd86%26bgColorOverlay%3Daaaaaa%26bgTextureOverlay%3D01_flat.png%26bgImgOpacityOverlay%3D0%26opacityOverlay%3D30%26bgColorShadow%3Daaaaaa%26bgTextureShadow%3D01_flat.png%26bgImgOpacityShadow%3D0%26opacityShadow%3D30%26thicknessShadow%3D8px%26offsetTopShadow%3D-8px%26offsetLeftShadow%3D-8px%26cornerRadiusShadow%3D8px&scope=&t-name=mint-choc&ui-version=1.8.2,download=true&files%5B%5D=ui.core.js&files%5B%5D=ui.widget.js&files%5B%5D=ui.mouse.js&files%5B%5D=ui.position.js&files%5B%5D=ui.draggable.js&files%5B%5D=ui.droppable.js&files%5B%5D=ui.resizable.js&files%5B%5D=ui.selectable.js&files%5B%5D=ui.sortable.js&files%5B%5D=ui.accordion.js&files%5B%5D=ui.autocomplete.js&files%5B%5D=ui.button.js&files%5B%5D=ui.dialog.js&files%5B%5D=ui.slider.js&files%5B%5D=ui.tabs.js&files%5B%5D=ui.datepicker.js&files%5B%5D=ui.progressbar.js&files%5B%5D=effects.core.js&files%5B%5D=effects.blind.js&files%5B%5D=effects.bounce.js&files%5B%5D=effects.clip.js&files%5B%5D=effects.drop.js&files%5B%5D=effects.explode.js&files%5B%5D=effects.fold.js&files%5B%5D=effects.highlight.js&files%5B%5D=effects.pulsate.js&files%5B%5D=effects.scale.js&files%5B%5D=effects.shake.js&files%5B%5D=effects.slide.js&files%5B%5D=effects.transfer.js&theme=%3FffDefault%3DVerdana%2C%2BArial%2C%2Bsans-serif%26fwDefault%3Dnormal%26fsDefault%3D1.1em%26cornerRadius%3D4px%26bgColorHeader%3D333333%26bgTextureHeader%3D08_diagonals_thick.png%26bgImgOpacityHeader%3D8%26borderColorHeader%3Da3a3a3%26fcHeader%3Deeeeee%26iconColorHeader%3Dbbbbbb%26bgColorContent%3Df9f9f9%26bgTextureContent%3D04_highlight_hard.png%26bgImgOpacityContent%3D100%26borderColorContent%3Dcccccc%26fcContent%3D222222%26iconColorContent%3D222222%26bgColorDefault%3D111111%26bgTextureDefault%3D02_glass.png%26bgImgOpacityDefault%3D40%26borderColorDefault%3D777777%26fcDefault%3De3e3e3%26iconColorDefault%3Dededed%26bgColorHover%3D1c1c1c%26bgTextureHover%3D02_glass.png%26bgImgOpacityHover%3D55%26borderColorHover%3D000000%26fcHover%3Dffffff%26iconColorHover%3Dffffff%26bgColorActive%3Dffffff%26bgTextureActive%3D01_flat.png%26bgImgOpacityActive%3D65%26borderColorActive%3Dcccccc%26fcActive%3D222222%26iconColorActive%3D222222%26bgColorHighlight%3Dffeb80%26bgTextureHighlight%3D06_inset_hard.png%26bgImgOpacityHighlight%3D55%26borderColorHighlight%3Dffde2e%26fcHighlight%3D363636%26iconColorHighlight%3D4ca300%26bgColorError%3Dcd0a0a%26bgTextureError%3D06_inset_hard.png%26bgImgOpacityError%3D45%26borderColorError%3D9e0505%26fcError%3Dffffff%26iconColorError%3Dffcf29%26bgColorOverlay%3Daaaaaa%26bgTextureOverlay%3D04_highlight_hard.png%26bgImgOpacityOverlay%3D40%26opacityOverlay%3D30%26bgColorShadow%3Daaaaaa%26bgTextureShadow%3D03_highlight_soft.png%26bgImgOpacityShadow%3D50%26opacityShadow%3D20%26thicknessShadow%3D8px%26offsetTopShadow%3D-8px%26offsetLeftShadow%3D-8px%26cornerRadiusShadow%3D8px&scope=&t-name=black-tie&ui-version=1.8.2,download=true&files%5B%5D=ui.core.js&files%5B%5D=ui.widget.js&files%5B%5D=ui.mouse.js&files%5B%5D=ui.position.js&files%5B%5D=ui.draggable.js&files%5B%5D=ui.droppable.js&files%5B%5D=ui.resizable.js&files%5B%5D=ui.selectable.js&files%5B%5D=ui.sortable.js&files%5B%5D=ui.accordion.js&files%5B%5D=ui.autocomplete.js&files%5B%5D=ui.button.js&files%5B%5D=ui.dialog.js&files%5B%5D=ui.slider.js&files%5B%5D=ui.tabs.js&files%5B%5D=ui.datepicker.js&files%5B%5D=ui.progressbar.js&files%5B%5D=effects.core.js&files%5B%5D=effects.blind.js&files%5B%5D=effects.bounce.js&files%5B%5D=effects.clip.js&files%5B%5D=effects.drop.js&files%5B%5D=effects.explode.js&files%5B%5D=effects.fold.js&files%5B%5D=effects.highlight.js&files%5B%5D=effects.pulsate.js&files%5B%5D=effects.scale.js&files%5B%5D=effects.shake.js&files%5B%5D=effects.slide.js&files%5B%5D=effects.transfer.js&theme=%3FffDefault%3DSegoe%2BUI%2C%2BHelvetica%2C%2BArial%2C%2Bsans-serif%26fwDefault%3Dbold%26fsDefault%3D1.1em%26cornerRadius%3D6px%26bgColorHeader%3D9fda58%26bgTextureHeader%3D12_gloss_wave.png%26bgImgOpacityHeader%3D85%26borderColorHeader%3D000000%26fcHeader%3D222222%26iconColorHeader%3D1f1f1f%26bgColorContent%3D000000%26bgTextureContent%3D12_gloss_wave.png%26bgImgOpacityContent%3D55%26borderColorContent%3D4a4a4a%26fcContent%3Dffffff%26iconColorContent%3D9fda58%26bgColorDefault%3D0a0a0a%26bgTextureDefault%3D02_glass.png%26bgImgOpacityDefault%3D40%26borderColorDefault%3D1b1613%26fcDefault%3Db8ec79%26iconColorDefault%3Db8ec79%26bgColorHover%3D000000%26bgTextureHover%3D02_glass.png%26bgImgOpacityHover%3D60%26borderColorHover%3D000000%26fcHover%3D96f226%26iconColorHover%3Db8ec79%26bgColorActive%3D4c4c4c%26bgTextureActive%3D01_flat.png%26bgImgOpacityActive%3D0%26borderColorActive%3D696969%26fcActive%3Dffffff%26iconColorActive%3Dffffff%26bgColorHighlight%3Df1fbe5%26bgTextureHighlight%3D02_glass.png%26bgImgOpacityHighlight%3D55%26borderColorHighlight%3D8cce3b%26fcHighlight%3D030303%26iconColorHighlight%3D000000%26bgColorError%3Df6ecd5%26bgTextureError%3D12_gloss_wave.png%26bgImgOpacityError%3D95%26borderColorError%3Df1ac88%26fcError%3D74736d%26iconColorError%3Dcd0a0a%26bgColorOverlay%3D262626%26bgTextureOverlay%3D07_diagonals_small.png%26bgImgOpacityOverlay%3D50%26opacityOverlay%3D30%26bgColorShadow%3D303030%26bgTextureShadow%3D01_flat.png%26bgImgOpacityShadow%3D0%26opacityShadow%3D50%26thicknessShadow%3D6px%26offsetTopShadow%3D-6px%26offsetLeftShadow%3D-6px%26cornerRadiusShadow%3D12px&scope=&t-name=trontastic&ui-version=1.8.2,download=true&files%5B%5D=ui.core.js&files%5B%5D=ui.widget.js&files%5B%5D=ui.mouse.js&files%5B%5D=ui.position.js&files%5B%5D=ui.draggable.js&files%5B%5D=ui.droppable.js&files%5B%5D=ui.resizable.js&files%5B%5D=ui.selectable.js&files%5B%5D=ui.sortable.js&files%5B%5D=ui.accordion.js&files%5B%5D=ui.autocomplete.js&files%5B%5D=ui.button.js&files%5B%5D=ui.dialog.js&files%5B%5D=ui.slider.js&files%5B%5D=ui.tabs.js&files%5B%5D=ui.datepicker.js&files%5B%5D=ui.progressbar.js&files%5B%5D=effects.core.js&files%5B%5D=effects.blind.js&files%5B%5D=effects.bounce.js&files%5B%5D=effects.clip.js&files%5B%5D=effects.drop.js&files%5B%5D=effects.explode.js&files%5B%5D=effects.fold.js&files%5B%5D=effects.highlight.js&files%5B%5D=effects.pulsate.js&files%5B%5D=effects.scale.js&files%5B%5D=effects.shake.js&files%5B%5D=effects.slide.js&files%5B%5D=effects.transfer.js&theme=%3FffDefault%3DGeorgia%252C%2BVerdana%252CArial%252Csans-serif%26fwDefault%3Dbold%26fsDefault%3D1.2em%26cornerRadius%3D5px%26bgColorHeader%3D261803%26bgTextureHeader%3D13_diamond.png%26bgImgOpacityHeader%3D8%26borderColorHeader%3Dbaaa5a%26fcHeader%3Deacd86%26iconColorHeader%3De9cd86%26bgColorContent%3D443113%26bgTextureContent%3D13_diamond.png%26bgImgOpacityContent%3D8%26borderColorContent%3Defec9f%26fcContent%3Defec9f%26iconColorContent%3Defec9f%26bgColorDefault%3D4f4221%26bgTextureDefault%3D13_diamond.png%26bgImgOpacityDefault%3D10%26borderColorDefault%3D362917%26fcDefault%3Df8eec9%26iconColorDefault%3De8e2b5%26bgColorHover%3D675423%26bgTextureHover%3D13_diamond.png%26bgImgOpacityHover%3D25%26borderColorHover%3D362917%26fcHover%3Df8eec9%26iconColorHover%3Df2ec64%26bgColorActive%3D443113%26bgTextureActive%3D13_diamond.png%26bgImgOpacityActive%3D8%26borderColorActive%3Defec9f%26fcActive%3Df9f2bd%26iconColorActive%3Df9f2bd%26bgColorHighlight%3Dd5ac5d%26bgTextureHighlight%3D13_diamond.png%26bgImgOpacityHighlight%3D25%26borderColorHighlight%3D362917%26fcHighlight%3D060200%26iconColorHighlight%3D070603%26bgColorError%3Dfee4bd%26bgTextureError%3D04_highlight_hard.png%26bgImgOpacityError%3D65%26borderColorError%3Dc26629%26fcError%3D803f1e%26iconColorError%3Dff7519%26bgColorOverlay%3D372806%26bgTextureOverlay%3D13_diamond.png%26bgImgOpacityOverlay%3D20%26opacityOverlay%3D80%26bgColorShadow%3Dddd4b0%26bgTextureShadow%3D01_flat.png%26bgImgOpacityShadow%3D75%26opacityShadow%3D30%26thicknessShadow%3D8px%26offsetTopShadow%3D-8px%26offsetLeftShadow%3D-8px%26cornerRadiusShadow%3D12px&scope=&t-name=swanky-purse&ui-version=1.8.2 ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/accordion/collapsible.html ================================================ jQuery UI Accordion - Collapse content

      Section 1

      Mauris mauris ante, blandit et, ultrices a, suscipit eget, quam. Integer ut neque. Vivamus nisi metus, molestie vel, gravida in, condimentum sit amet, nunc. Nam a nibh. Donec suscipit eros. Nam mi. Proin viverra leo ut odio. Curabitur malesuada. Vestibulum a velit eu ante scelerisque vulputate.

      Section 2

      Sed non urna. Donec et ante. Phasellus eu ligula. Vestibulum sit amet purus. Vivamus hendrerit, dolor at aliquet laoreet, mauris turpis porttitor velit, faucibus interdum tellus libero ac justo. Vivamus non quam. In suscipit faucibus urna.

      Section 3

      Nam enim risus, molestie et, porta ac, aliquam ac, risus. Quisque lobortis. Phasellus pellentesque purus in massa. Aenean in pede. Phasellus ac libero ac tellus pellentesque semper. Sed ac felis. Sed commodo, magna quis lacinia ornare, quam ante aliquam nisi, eu iaculis leo purus venenatis dui.

      • List item one
      • List item two
      • List item three

      Section 4

      Cras dictum. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aenean lacinia mauris vel est.

      Suspendisse eu nisl. Nullam ut libero. Integer dignissim consequat lectus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.

      By default, accordions always keep one section open. To allow for all sections to be be collapsible, set the collapsible option to true. Click on the currently open section to collapse its content pane.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/accordion/custom-icons.html ================================================ jQuery UI Accordion - Customize icons

      Section 1

      Mauris mauris ante, blandit et, ultrices a, suscipit eget, quam. Integer ut neque. Vivamus nisi metus, molestie vel, gravida in, condimentum sit amet, nunc. Nam a nibh. Donec suscipit eros. Nam mi. Proin viverra leo ut odio. Curabitur malesuada. Vestibulum a velit eu ante scelerisque vulputate.

      Section 2

      Sed non urna. Donec et ante. Phasellus eu ligula. Vestibulum sit amet purus. Vivamus hendrerit, dolor at aliquet laoreet, mauris turpis porttitor velit, faucibus interdum tellus libero ac justo. Vivamus non quam. In suscipit faucibus urna.

      Section 3

      Nam enim risus, molestie et, porta ac, aliquam ac, risus. Quisque lobortis. Phasellus pellentesque purus in massa. Aenean in pede. Phasellus ac libero ac tellus pellentesque semper. Sed ac felis. Sed commodo, magna quis lacinia ornare, quam ante aliquam nisi, eu iaculis leo purus venenatis dui.

      • List item one
      • List item two
      • List item three

      Section 4

      Cras dictum. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aenean lacinia mauris vel est.

      Suspendisse eu nisl. Nullam ut libero. Integer dignissim consequat lectus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.

      Customize the header icons with the icons option, which accepts classes for the header's default and active (open) state. Use any class from the UI CSS framework, or create custom classes with background images.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/accordion/default.html ================================================ jQuery UI Accordion - Default functionality

      Section 1

      Mauris mauris ante, blandit et, ultrices a, suscipit eget, quam. Integer ut neque. Vivamus nisi metus, molestie vel, gravida in, condimentum sit amet, nunc. Nam a nibh. Donec suscipit eros. Nam mi. Proin viverra leo ut odio. Curabitur malesuada. Vestibulum a velit eu ante scelerisque vulputate.

      Section 2

      Sed non urna. Donec et ante. Phasellus eu ligula. Vestibulum sit amet purus. Vivamus hendrerit, dolor at aliquet laoreet, mauris turpis porttitor velit, faucibus interdum tellus libero ac justo. Vivamus non quam. In suscipit faucibus urna.

      Section 3

      Nam enim risus, molestie et, porta ac, aliquam ac, risus. Quisque lobortis. Phasellus pellentesque purus in massa. Aenean in pede. Phasellus ac libero ac tellus pellentesque semper. Sed ac felis. Sed commodo, magna quis lacinia ornare, quam ante aliquam nisi, eu iaculis leo purus venenatis dui.

      • List item one
      • List item two
      • List item three

      Section 4

      Cras dictum. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aenean lacinia mauris vel est.

      Suspendisse eu nisl. Nullam ut libero. Integer dignissim consequat lectus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.

      Click headers to expand/collapse content that is broken into logical sections, much like tabs. Optionally, toggle sections open/closed on mouseover.

      The underlying HTML markup is a series of headers (H3 tags) and content divs so the content is usable without JavaScript.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/accordion/fillspace.html ================================================ jQuery UI Accordion - Fill space

      Resize the outer container:

      Section 1

      Mauris mauris ante, blandit et, ultrices a, suscipit eget, quam. Integer ut neque. Vivamus nisi metus, molestie vel, gravida in, condimentum sit amet, nunc. Nam a nibh. Donec suscipit eros. Nam mi. Proin viverra leo ut odio. Curabitur malesuada. Vestibulum a velit eu ante scelerisque vulputate.

      Section 2

      Sed non urna. Donec et ante. Phasellus eu ligula. Vestibulum sit amet purus. Vivamus hendrerit, dolor at aliquet laoreet, mauris turpis porttitor velit, faucibus interdum tellus libero ac justo. Vivamus non quam. In suscipit faucibus urna.

      Section 3

      Nam enim risus, molestie et, porta ac, aliquam ac, risus. Quisque lobortis. Phasellus pellentesque purus in massa. Aenean in pede. Phasellus ac libero ac tellus pellentesque semper. Sed ac felis. Sed commodo, magna quis lacinia ornare, quam ante aliquam nisi, eu iaculis leo purus venenatis dui.

      • List item one
      • List item two
      • List item three

      Section 4

      Cras dictum. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aenean lacinia mauris vel est.

      Suspendisse eu nisl. Nullam ut libero. Integer dignissim consequat lectus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.

      I'm another panel

      Because the accordion is comprised of block-level elements, by default its width fills the available horizontal space. To fill the vertical space allocated by its container, set the heightStyle option to "fill", and the script will automatically set the dimensions of the accordion to the height of its parent container.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/accordion/hoverintent.html ================================================ jQuery UI Accordion - Open on hoverintent

      Section 1

      Mauris mauris ante, blandit et, ultrices a, suscipit eget, quam. Integer ut neque. Vivamus nisi metus, molestie vel, gravida in, condimentum sit amet, nunc. Nam a nibh. Donec suscipit eros. Nam mi. Proin viverra leo ut odio. Curabitur malesuada. Vestibulum a velit eu ante scelerisque vulputate.

      Section 2

      Sed non urna. Donec et ante. Phasellus eu ligula. Vestibulum sit amet purus. Vivamus hendrerit, dolor at aliquet laoreet, mauris turpis porttitor velit, faucibus interdum tellus libero ac justo. Vivamus non quam. In suscipit faucibus urna.

      Section 3

      Nam enim risus, molestie et, porta ac, aliquam ac, risus. Quisque lobortis. Phasellus pellentesque purus in massa. Aenean in pede. Phasellus ac libero ac tellus pellentesque semper. Sed ac felis. Sed commodo, magna quis lacinia ornare, quam ante aliquam nisi, eu iaculis leo purus venenatis dui.

      • List item one
      • List item two
      • List item three

      Section 4

      Cras dictum. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aenean lacinia mauris vel est.

      Suspendisse eu nisl. Nullam ut libero. Integer dignissim consequat lectus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.

      Click headers to expand/collapse content that is broken into logical sections, much like tabs. Optionally, toggle sections open/closed on mouseover.

      The underlying HTML markup is a series of headers (H3 tags) and content divs so the content is usable without JavaScript.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/accordion/index.html ================================================ jQuery UI Accordion Demos ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/accordion/no-auto-height.html ================================================ jQuery UI Accordion - No auto height

      Section 1

      Mauris mauris ante, blandit et, ultrices a, susceros. Nam mi. Proin viverra leo ut odio. Curabitur malesuada. Vestibulum a velit eu ante scelerisque vulputate.

      Section 2

      Sed non urna. Donec et ante. Phasellus eu ligula. Vestibulum sit amet purus. Vivamus hendrerit, dolor at aliquet laoreet, mauris turpis porttitor velit, faucibus interdum tellus libero ac justo. Vivamus non quam. In suscipit faucibus urna.

      Section 3

      Nam enim risus, molestie et, porta ac, aliquam ac, risus. Quisque lobortis. Phasellus pellentesque purus in massa. Aenean in pede. Phasellus ac libero ac tellus pellentesque semper. Sed ac felis. Sed commodo, magna quis lacinia ornare, quam ante aliquam nisi, eu iaculis leo purus venenatis dui.

      • List item
      • List item
      • List item
      • List item
      • List item
      • List item
      • List item

      Setting heightStyle: "content" allows the accordion panels to keep their native height.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/accordion/sortable.html ================================================ jQuery UI Accordion - Sortable

      Section 1

      Mauris mauris ante, blandit et, ultrices a, suscipit eget, quam. Integer ut neque. Vivamus nisi metus, molestie vel, gravida in, condimentum sit amet, nunc. Nam a nibh. Donec suscipit eros. Nam mi. Proin viverra leo ut odio. Curabitur malesuada. Vestibulum a velit eu ante scelerisque vulputate.

      Section 2

      Sed non urna. Donec et ante. Phasellus eu ligula. Vestibulum sit amet purus. Vivamus hendrerit, dolor at aliquet laoreet, mauris turpis porttitor velit, faucibus interdum tellus libero ac justo. Vivamus non quam. In suscipit faucibus urna.

      Section 3

      Nam enim risus, molestie et, porta ac, aliquam ac, risus. Quisque lobortis. Phasellus pellentesque purus in massa. Aenean in pede. Phasellus ac libero ac tellus pellentesque semper. Sed ac felis. Sed commodo, magna quis lacinia ornare, quam ante aliquam nisi, eu iaculis leo purus venenatis dui.

      • List item one
      • List item two
      • List item three

      Section 4

      Cras dictum. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aenean lacinia mauris vel est.

      Suspendisse eu nisl. Nullam ut libero. Integer dignissim consequat lectus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.

      Drag the header to re-order panels.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/addClass/default.html ================================================ jQuery UI Effects - addClass demo
      Etiam libero neque, luctus a, eleifend nec, semper at, lorem. Sed pede.
      Run Effect

      This demo adds a class which animates: text-indent, letter-spacing, width, height, padding, margin, and font-size.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/addClass/index.html ================================================ jQuery UI Effects Demos ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/animate/default.html ================================================ jQuery UI Effects - Animate demo

      Animate

      Etiam libero neque, luctus a, eleifend nec, semper at, lorem. Sed pede. Nulla lorem metus, adipiscing ut, luctus sed, hendrerit vitae, mi.

      Toggle Effect

      Click the button above to preview the effect.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/animate/index.html ================================================ jQuery UI Effects Demos ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/autocomplete/categories.html ================================================ jQuery UI Autocomplete - Categories

      A categorized search result. Try typing "a" or "n".

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/autocomplete/combobox.html ================================================ jQuery UI Autocomplete - Combobox

      A custom widget built by composition of Autocomplete and Button. You can either type something into the field to get filtered suggestions based on your input, or use the button to get the full list of selections.

      The input is read from an existing select-element for progressive enhancement, passed to Autocomplete with a customized source-option.

      This is not a supported or even complete widget. Its purely for demoing what autocomplete can do with a bit of customization. For a detailed explanation of how the widget works, check out this Learning jQuery article.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/autocomplete/custom-data.html ================================================ jQuery UI Autocomplete - Custom data and display
      Select a project (type "j" for a start):

      You can use your own custom data formats and displays by simply overriding the default focus and select actions.

      Try typing "j" to get a list of projects or just press the down arrow.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/autocomplete/default.html ================================================ jQuery UI Autocomplete - Default functionality

      The Autocomplete widgets provides suggestions while you type into the field. Here the suggestions are tags for programming languages, give "ja" (for Java or JavaScript) a try.

      The datasource is a simple JavaScript array, provided to the widget using the source-option.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/autocomplete/folding.html ================================================ jQuery UI Autocomplete - Accent folding

      The autocomplete field uses a custom source option which will match results that have accented characters even when the text field doesn't contain accented characters. However if the you type in accented characters in the text field it is smart enough not to show results that aren't accented.

      Try typing "Jo" to see "John" and "Jörn", then type "Jö" to see only "Jörn".

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/autocomplete/index.html ================================================ jQuery UI Autocomplete Demos ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/autocomplete/london.xml ================================================ 6987 London 51.5084152563931 -0.125532746315002 2643743 GB United Kingdom P PPLC London 42.983389283 -81.233042387 6058560 CA Canada P PPL East London -33.0152850934643 27.9116249084473 1006984 ZA South Africa P PPL City 51.5133363996235 -0.0890064239501953 2643744 GB United Kingdom A ADM2 London 37.1289771 -84.0832646 4298960 US United States P PPL The Tower of London 51.5082349601834 -0.0763034820556641 6286786 GB United Kingdom S CSTL London Reefs 8.85 112.5333333 1879967 U RFSU Greater London 51.5 -0.1666667 2648110 GB United Kingdom A ADM2 London 46.1666667 6.0166667 2661811 CH Switzerland H STM London Borough of Islington 51.5333333 -0.1333333 3333156 GB United Kingdom A ADM2 ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/autocomplete/maxheight.html ================================================ jQuery UI Autocomplete - Scrollable results

      When displaying a long list of options, you can simply set the max-height for the autocomplete menu to prevent the menu from growing too large. Try typing "a" or "s" above to get a long list of results that you can scroll through.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/autocomplete/multiple-remote.html ================================================ jQuery UI Autocomplete - Multiple, remote

      Usage: Enter at least two characters to get bird name suggestions. Select a value to continue adding more names.

      This is an example showing how to use the source-option along with some events to enable autocompleting multiple values into a single field.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/autocomplete/multiple.html ================================================ jQuery UI Autocomplete - Multiple values

      Usage: Type something, eg. "j" to see suggestions for tagging with programming languages. Select a value, then continue typing to add more.

      This is an example showing how to use the source-option along with some events to enable autocompleting multiple values into a single field.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/autocomplete/remote-jsonp.html ================================================ jQuery UI Autocomplete - Remote JSONP datasource
      Powered by geonames.org
      Result:

      The Autocomplete widgets provides suggestions while you type into the field. Here the suggestions are cities, displayed when at least two characters are entered into the field.

      In this case, the datasource is the geonames.org webservice. While only the city name itself ends up in the input after selecting an element, more info is displayed in the suggestions to help find the right entry. That data is also available in callbacks, as illustrated by the Result area below the input.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/autocomplete/remote-with-cache.html ================================================ jQuery UI Autocomplete - Remote with caching

      The Autocomplete widgets provides suggestions while you type into the field. Here the suggestions are bird names, displayed when at least two characters are entered into the field.

      Similar to the remote datasource demo, though this adds some local caching to improve performance. The cache here saves just one query, and could be extended to cache multiple values, one for each term.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/autocomplete/remote.html ================================================ jQuery UI Autocomplete - Remote datasource
      Result:

      The Autocomplete widgets provides suggestions while you type into the field. Here the suggestions are bird names, displayed when at least two characters are entered into the field.

      The datasource is a server-side script which returns JSON data, specified via a simple URL for the source-option. In addition, the minLength-option is set to 2 to avoid queries that would return too many results and the select-event is used to display some feedback.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/autocomplete/search.php ================================================ "Botaurus stellaris", "Little Grebe"=>"Tachybaptus ruficollis", "Black-necked Grebe"=>"Podiceps nigricollis", "Little Bittern"=>"Ixobrychus minutus", "Black-crowned Night Heron"=>"Nycticorax nycticorax", "Purple Heron"=>"Ardea purpurea", "White Stork"=>"Ciconia ciconia", "Spoonbill"=>"Platalea leucorodia", "Red-crested Pochard"=>"Netta rufina", "Common Eider"=>"Somateria mollissima", "Red Kite"=>"Milvus milvus", "Hen Harrier"=>"Circus cyaneus", "Montagu`s Harrier"=>"Circus pygargus", "Black Grouse"=>"Tetrao tetrix", "Grey Partridge"=>"Perdix perdix", "Spotted Crake"=>"Porzana porzana", "Corncrake"=>"Crex crex", "Common Crane"=>"Grus grus", "Avocet"=>"Recurvirostra avosetta", "Stone Curlew"=>"Burhinus oedicnemus", "Common Ringed Plover"=>"Charadrius hiaticula", "Kentish Plover"=>"Charadrius alexandrinus", "Ruff"=>"Philomachus pugnax", "Common Snipe"=>"Gallinago gallinago", "Black-tailed Godwit"=>"Limosa limosa", "Common Redshank"=>"Tringa totanus", "Sandwich Tern"=>"Sterna sandvicensis", "Common Tern"=>"Sterna hirundo", "Arctic Tern"=>"Sterna paradisaea", "Little Tern"=>"Sternula albifrons", "Black Tern"=>"Chlidonias niger", "Barn Owl"=>"Tyto alba", "Little Owl"=>"Athene noctua", "Short-eared Owl"=>"Asio flammeus", "European Nightjar"=>"Caprimulgus europaeus", "Common Kingfisher"=>"Alcedo atthis", "Eurasian Hoopoe"=>"Upupa epops", "Eurasian Wryneck"=>"Jynx torquilla", "European Green Woodpecker"=>"Picus viridis", "Crested Lark"=>"Galerida cristata", "White-headed Duck"=>"Oxyura leucocephala", "Pale-bellied Brent Goose"=>"Branta hrota", "Tawny Pipit"=>"Anthus campestris", "Whinchat"=>"Saxicola rubetra", "European Stonechat"=>"Saxicola rubicola", "Northern Wheatear"=>"Oenanthe oenanthe", "Savi`s Warbler"=>"Locustella luscinioides", "Sedge Warbler"=>"Acrocephalus schoenobaenus", "Great Reed Warbler"=>"Acrocephalus arundinaceus", "Bearded Reedling"=>"Panurus biarmicus", "Red-backed Shrike"=>"Lanius collurio", "Great Grey Shrike"=>"Lanius excubitor", "Woodchat Shrike"=>"Lanius senator", "Common Raven"=>"Corvus corax", "Yellowhammer"=>"Emberiza citrinella", "Ortolan Bunting"=>"Emberiza hortulana", "Corn Bunting"=>"Emberiza calandra", "Great Cormorant"=>"Phalacrocorax carbo", "Hawfinch"=>"Coccothraustes coccothraustes", "Common Shelduck"=>"Tadorna tadorna", "Bluethroat"=>"Luscinia svecica", "Grey Heron"=>"Ardea cinerea", "Barn Swallow"=>"Hirundo rustica", "Hooded Crow"=>"Corvus cornix", "Dunlin"=>"Calidris alpina", "Eurasian Pied Flycatcher"=>"Ficedula hypoleuca", "Eurasian Nuthatch"=>"Sitta europaea", "Short-toed Tree Creeper"=>"Certhia brachydactyla", "Wood Lark"=>"Lullula arborea", "Tree Pipit"=>"Anthus trivialis", "Eurasian Hobby"=>"Falco subbuteo", "Marsh Warbler"=>"Acrocephalus palustris", "Wood Sandpiper"=>"Tringa glareola", "Tawny Owl"=>"Strix aluco", "Lesser Whitethroat"=>"Sylvia curruca", "Barnacle Goose"=>"Branta leucopsis", "Common Goldeneye"=>"Bucephala clangula", "Western Marsh Harrier"=>"Circus aeruginosus", "Common Buzzard"=>"Buteo buteo", "Sanderling"=>"Calidris alba", "Little Gull"=>"Larus minutus", "Eurasian Magpie"=>"Pica pica", "Willow Warbler"=>"Phylloscopus trochilus", "Wood Warbler"=>"Phylloscopus sibilatrix", "Great Crested Grebe"=>"Podiceps cristatus", "Eurasian Jay"=>"Garrulus glandarius", "Common Redstart"=>"Phoenicurus phoenicurus", "Blue-headed Wagtail"=>"Motacilla flava", "Common Swift"=>"Apus apus", "Marsh Tit"=>"Poecile palustris", "Goldcrest"=>"Regulus regulus", "European Golden Plover"=>"Pluvialis apricaria", "Eurasian Bullfinch"=>"Pyrrhula pyrrhula", "Common Whitethroat"=>"Sylvia communis", "Meadow Pipit"=>"Anthus pratensis", "Greylag Goose"=>"Anser anser", "Spotted Flycatcher"=>"Muscicapa striata", "European Greenfinch"=>"Carduelis chloris", "Common Greenshank"=>"Tringa nebularia", "Great Spotted Woodpecker"=>"Dendrocopos major", "Greater Canada Goose"=>"Branta canadensis", "Mistle Thrush"=>"Turdus viscivorus", "Great Black-backed Gull"=>"Larus marinus", "Goosander"=>"Mergus merganser", "Great Egret"=>"Casmerodius albus", "Northern Goshawk"=>"Accipiter gentilis", "Dunnock"=>"Prunella modularis", "Stock Dove"=>"Columba oenas", "Common Wood Pigeon"=>"Columba palumbus", "Eurasian Woodcock"=>"Scolopax rusticola", "House Sparrow"=>"Passer domesticus", "Common House Martin"=>"Delichon urbicum", "Red Knot"=>"Calidris canutus", "Western Jackdaw"=>"Corvus monedula", "Brambling"=>"Fringilla montifringilla", "Northern Lapwing"=>"Vanellus vanellus", "European Reed Warbler"=>"Acrocephalus scirpaceus", "Lesser Black-backed Gull"=>"Larus fuscus", "Little Egret"=>"Egretta garzetta", "Little Stint"=>"Calidris minuta", "Common Linnet"=>"Carduelis cannabina", "Mute Swan"=>"Cygnus olor", "Common Cuckoo"=>"Cuculus canorus", "Black-headed Gull"=>"Larus ridibundus", "Greater White-fronted Goose"=>"Anser albifrons", "Great Tit"=>"Parus major", "Redwing"=>"Turdus iliacus", "Gadwall"=>"Anas strepera", "Fieldfare"=>"Turdus pilaris", "Tufted Duck"=>"Aythya fuligula", "Crested Tit"=>"Lophophanes cristatus", "Willow Tit"=>"Poecile montanus", "Eurasian Coot"=>"Fulica atra", "Common Blackbird"=>"Turdus merula", "Smew"=>"Mergus albellus", "Common Sandpiper"=>"Actitis hypoleucos", "Sand Martin"=>"Riparia riparia", "Purple Sandpiper"=>"Calidris maritima", "Northern Pintail"=>"Anas acuta", "Blue Tit"=>"Cyanistes caeruleus", "European Goldfinch"=>"Carduelis carduelis", "Eurasian Whimbrel"=>"Numenius phaeopus", "Common Reed Bunting"=>"Emberiza schoeniclus", "Eurasian Tree Sparrow"=>"Passer montanus", "Rook"=>"Corvus frugilegus", "European Robin"=>"Erithacus rubecula", "Bar-tailed Godwit"=>"Limosa lapponica", "Dark-bellied Brent Goose"=>"Branta bernicla", "Eurasian Oystercatcher"=>"Haematopus ostralegus", "Eurasian Siskin"=>"Carduelis spinus", "Northern Shoveler"=>"Anas clypeata", "Eurasian Wigeon"=>"Anas penelope", "Eurasian Sparrow Hawk"=>"Accipiter nisus", "Icterine Warbler"=>"Hippolais icterina", "Common Starling"=>"Sturnus vulgaris", "Long-tailed Tit"=>"Aegithalos caudatus", "Ruddy Turnstone"=>"Arenaria interpres", "Mew Gull"=>"Larus canus", "Common Pochard"=>"Aythya ferina", "Common Chiffchaff"=>"Phylloscopus collybita", "Greater Scaup"=>"Aythya marila", "Common Kestrel"=>"Falco tinnunculus", "Garden Warbler"=>"Sylvia borin", "Eurasian Collared Dove"=>"Streptopelia decaocto", "Eurasian Skylark"=>"Alauda arvensis", "Common Chaffinch"=>"Fringilla coelebs", "Common Moorhen"=>"Gallinula chloropus", "Water Pipit"=>"Anthus spinoletta", "Mallard"=>"Anas platyrhynchos", "Winter Wren"=>"Troglodytes troglodytes", "Common Teal"=>"Anas crecca", "Green Sandpiper"=>"Tringa ochropus", "White Wagtail"=>"Motacilla alba", "Eurasian Curlew"=>"Numenius arquata", "Song Thrush"=>"Turdus philomelos", "European Herring Gull"=>"Larus argentatus", "Grey Plover"=>"Pluvialis squatarola", "Carrion Crow"=>"Corvus corone", "Coal Tit"=>"Periparus ater", "Spotted Redshank"=>"Tringa erythropus", "Blackcap"=>"Sylvia atricapilla", "Egyptian Vulture"=>"Neophron percnopterus", "Razorbill"=>"Alca torda", "Alpine Swift"=>"Apus melba", "Long-legged Buzzard"=>"Buteo rufinus", "Audouin`s Gull"=>"Larus audouinii", "Balearic Shearwater"=>"Puffinus mauretanicus", "Upland Sandpiper"=>"Bartramia longicauda", "Greater Spotted Eagle"=>"Aquila clanga", "Ring Ouzel"=>"Turdus torquatus", "Yellow-browed Warbler"=>"Phylloscopus inornatus", "Blue Rock Thrush"=>"Monticola solitarius", "Buff-breasted Sandpiper"=>"Tryngites subruficollis", "Jack Snipe"=>"Lymnocryptes minimus", "White-rumped Sandpiper"=>"Calidris fuscicollis", "Ruddy Shelduck"=>"Tadorna ferruginea", "Cetti's Warbler"=>"Cettia cetti", "Citrine Wagtail"=>"Motacilla citreola", "Roseate Tern"=>"Sterna dougallii", "Black-legged Kittiwake"=>"Rissa tridactyla", "Pygmy Cormorant"=>"Phalacrocorax pygmeus", "Booted Eagle"=>"Aquila pennata", "Lesser White-fronted Goose"=>"Anser erythropus", "Little Bunting"=>"Emberiza pusilla", "Eleonora's Falcon"=>"Falco eleonorae", "European Serin"=>"Serinus serinus", "Twite"=>"Carduelis flavirostris", "Yellow-legged Gull"=>"Larus michahellis", "Gyr Falcon"=>"Falco rusticolus", "Greenish Warbler"=>"Phylloscopus trochiloides", "Red-necked Phalarope"=>"Phalaropus lobatus", "Mealy Redpoll"=>"Carduelis flammea", "Glaucous Gull"=>"Larus hyperboreus", "Great Skua"=>"Stercorarius skua", "Great Bustard"=>"Otis tarda", "Velvet Scoter"=>"Melanitta fusca", "Pine Grosbeak"=>"Pinicola enucleator", "House Crow"=>"Corvus splendens", "Hume`s Leaf Warbler"=>"Phylloscopus humei", "Great Northern Loon"=>"Gavia immer", "Long-tailed Duck"=>"Clangula hyemalis", "Lapland Longspur"=>"Calcarius lapponicus", "Northern Gannet"=>"Morus bassanus", "Eastern Imperial Eagle"=>"Aquila heliaca", "Little Auk"=>"Alle alle", "Lesser Spotted Woodpecker"=>"Dendrocopos minor", "Iceland Gull"=>"Larus glaucoides", "Parasitic Jaeger"=>"Stercorarius parasiticus", "Bewick`s Swan"=>"Cygnus bewickii", "Little Bustard"=>"Tetrax tetrax", "Little Crake"=>"Porzana parva", "Baillon`s Crake"=>"Porzana pusilla", "Long-tailed Jaeger"=>"Stercorarius longicaudus", "King Eider"=>"Somateria spectabilis", "Greater Short-toed Lark"=>"Calandrella brachydactyla", "Houbara Bustard"=>"Chlamydotis undulata", "Curlew Sandpiper"=>"Calidris ferruginea", "Common Crossbill"=>"Loxia curvirostra", "European Shag"=>"Phalacrocorax aristotelis", "Horned Grebe"=>"Podiceps auritus", "Common Quail"=>"Coturnix coturnix", "Bearded Vulture"=>"Gypaetus barbatus", "Lanner Falcon"=>"Falco biarmicus", "Middle Spotted Woodpecker"=>"Dendrocopos medius", "Pomarine Jaeger"=>"Stercorarius pomarinus", "Red-breasted Merganser"=>"Mergus serrator", "Eurasian Black Vulture"=>"Aegypius monachus", "Eurasian Dotterel"=>"Charadrius morinellus", "Common Nightingale"=>"Luscinia megarhynchos", "Northern willow warbler"=>"Phylloscopus trochilus acredula", "Manx Shearwater"=>"Puffinus puffinus", "Northern Fulmar"=>"Fulmarus glacialis", "Eurasian Eagle Owl"=>"Bubo bubo", "Orphean Warbler"=>"Sylvia hortensis", "Melodious Warbler"=>"Hippolais polyglotta", "Pallas's Leaf Warbler"=>"Phylloscopus proregulus", "Atlantic Puffin"=>"Fratercula arctica", "Black-throated Loon"=>"Gavia arctica", "Bohemian Waxwing"=>"Bombycilla garrulus", "Marsh Sandpiper"=>"Tringa stagnatilis", "Great Snipe"=>"Gallinago media", "Squacco Heron"=>"Ardeola ralloides", "Long-eared Owl"=>"Asio otus", "Caspian Tern"=>"Hydroprogne caspia", "Red-breasted Goose"=>"Branta ruficollis", "Red-throated Loon"=>"Gavia stellata", "Common Rosefinch"=>"Carpodacus erythrinus", "Red-footed Falcon"=>"Falco vespertinus", "Ross's Goose"=>"Anser rossii", "Red Phalarope"=>"Phalaropus fulicarius", "Pied Wagtail"=>"Motacilla yarrellii", "Rose-coloured Starling"=>"Sturnus roseus", "Rough-legged Buzzard"=>"Buteo lagopus", "Saker Falcon"=>"Falco cherrug", "European Roller"=>"Coracias garrulus", "Short-toed Eagle"=>"Circaetus gallicus", "Peregrine Falcon"=>"Falco peregrinus", "Merlin"=>"Falco columbarius", "Snow Goose"=>"Anser caerulescens", "Snowy Owl"=>"Bubo scandiacus", "Snow Bunting"=>"Plectrophenax nivalis", "Common Grasshopper Warbler"=>"Locustella naevia", "Golden Eagle"=>"Aquila chrysaetos", "Black-winged Stilt"=>"Himantopus himantopus", "Steppe Eagle"=>"Aquila nipalensis", "Pallid Harrier"=>"Circus macrourus", "European Storm-petrel"=>"Hydrobates pelagicus", "Horned Lark"=>"Eremophila alpestris", "Eurasian Treecreeper"=>"Certhia familiaris", "Taiga Bean Goose"=>"Anser fabalis", "Temminck`s Stint"=>"Calidris temminckii", "Terek Sandpiper"=>"Xenus cinereus", "Tundra Bean Goose"=>"Anser serrirostris", "European Turtle Dove"=>"Streptopelia turtur", "Leach`s Storm-petrel"=>"Oceanodroma leucorhoa", "Eurasian Griffon Vulture"=>"Gyps fulvus", "Paddyfield Warbler"=>"Acrocephalus agricola", "Osprey"=>"Pandion haliaetus", "Firecrest"=>"Regulus ignicapilla", "Water Rail"=>"Rallus aquaticus", "European Honey Buzzard"=>"Pernis apivorus", "Eurasian Golden Oriole"=>"Oriolus oriolus", "Whooper Swan"=>"Cygnus cygnus", "Two-barred Crossbill"=>"Loxia leucoptera", "White-tailed Eagle"=>"Haliaeetus albicilla", "Atlantic Murre"=>"Uria aalge", "Garganey"=>"Anas querquedula", "Black Redstart"=>"Phoenicurus ochruros", "Common Scoter"=>"Melanitta nigra", "Rock Pipit"=>"Anthus petrosus", "Lesser Spotted Eagle"=>"Aquila pomarina", "Cattle Egret"=>"Bubulcus ibis", "White-winged Black Tern"=>"Chlidonias leucopterus", "Black Stork"=>"Ciconia nigra", "Mediterranean Gull"=>"Larus melanocephalus", "Black Kite"=>"Milvus migrans", "Yellow Wagtail"=>"Motacilla flavissima", "Red-necked Grebe"=>"Podiceps grisegena", "Gull-billed Tern"=>"Gelochelidon nilotica", "Pectoral Sandpiper"=>"Calidris melanotos", "Barred Warbler"=>"Sylvia nisoria", "Red-throated Pipit"=>"Anthus cervinus", "Grey Wagtail"=>"Motacilla cinerea", "Richard`s Pipit"=>"Anthus richardi", "Black Woodpecker"=>"Dryocopus martius", "Little Ringed Plover"=>"Charadrius dubius", "Whiskered Tern"=>"Chlidonias hybrida", "Lesser Redpoll"=>"Carduelis cabaret", "Pallas' Bunting"=>"Emberiza pallasi", "Ferruginous Duck"=>"Aythya nyroca", "Whistling Swan"=>"Cygnus columbianus", "Black Brant"=>"Branta nigricans", "Marbled Teal"=>"Marmaronetta angustirostris", "Canvasback"=>"Aythya valisineria", "Redhead"=>"Aythya americana", "Lesser Scaup"=>"Aythya affinis", "Steller`s Eider"=>"Polysticta stelleri", "Spectacled Eider"=>"Somateria fischeri", "Harlequin Duck"=>"Histronicus histrionicus", "Black Scoter"=>"Melanitta americana", "Surf Scoter"=>"Melanitta perspicillata", "Barrow`s Goldeneye"=>"Bucephala islandica", "Falcated Duck"=>"Anas falcata", "American Wigeon"=>"Anas americana", "Blue-winged Teal"=>"Anas discors", "American Black Duck"=>"Anas rubripes", "Baikal Teal"=>"Anas formosa", "Green-Winged Teal"=>"Anas carolinensis", "Hazel Grouse"=>"Bonasa bonasia", "Rock Partridge"=>"Alectoris graeca", "Red-legged Partridge"=>"Alectoris rufa", "Yellow-billed Loon"=>"Gavia adamsii", "Cory`s Shearwater"=>"Calonectris borealis", "Madeiran Storm-Petrel"=>"Oceanodroma castro", "Great White Pelican"=>"Pelecanus onocrotalus", "Dalmatian Pelican"=>"Pelecanus crispus", "American Bittern"=>"Botaurus lentiginosus", "Glossy Ibis"=>"Plegadis falcinellus", "Spanish Imperial Eagle"=>"Aquila adalberti", "Lesser Kestrel"=>"Falco naumanni", "Houbara Bustard"=>"Chlamydotis undulata", "Crab-Plover"=>"Dromas ardeola", "Cream-coloured Courser"=>"Cursorius cursor", "Collared Pratincole"=>"Glareola pratincola", "Black-winged Pratincole"=>"Glareola nordmanni", "Killdeer"=>"Charadrius vociferus", "Lesser Sand Plover"=>"Charadrius mongolus", "Greater Sand Plover"=>"Charadrius leschenaultii", "Caspian Plover"=>"Charadrius asiaticus", "American Golden Plover"=>"Pluvialis dominica", "Pacific Golden Plover"=>"Pluvialis fulva", "Sharp-tailed Sandpiper"=>"Calidris acuminata", "Broad-billed Sandpiper"=>"Limicola falcinellus", "Spoon-Billed Sandpiper"=>"Eurynorhynchus pygmaeus", "Short-Billed Dowitcher"=>"Limnodromus griseus", "Long-billed Dowitcher"=>"Limnodromus scolopaceus", "Hudsonian Godwit"=>"Limosa haemastica", "Little Curlew"=>"Numenius minutus", "Lesser Yellowlegs"=>"Tringa flavipes", "Wilson`s Phalarope"=>"Phalaropus tricolor", "Pallas`s Gull"=>"Larus ichthyaetus", "Laughing Gull"=>"Larus atricilla", "Franklin`s Gull"=>"Larus pipixcan", "Bonaparte`s Gull"=>"Larus philadelphia", "Ring-billed Gull"=>"Larus delawarensis", "American Herring Gull"=>"Larus smithsonianus", "Caspian Gull"=>"Larus cachinnans", "Ivory Gull"=>"Pagophila eburnea", "Royal Tern"=>"Sterna maxima", "Brünnich`s Murre"=>"Uria lomvia", "Crested Auklet"=>"Aethia cristatella", "Parakeet Auklet"=>"Cyclorrhynchus psittacula", "Tufted Puffin"=>"Lunda cirrhata", "Laughing Dove"=>"Streptopelia senegalensis", "Great Spotted Cuckoo"=>"Clamator glandarius", "Great Grey Owl"=>"Strix nebulosa", "Tengmalm`s Owl"=>"Aegolius funereus", "Red-Necked Nightjar"=>"Caprimulgus ruficollis", "Chimney Swift"=>"Chaetura pelagica", "Green Bea-Eater"=>"Merops orientalis", "Grey-headed Woodpecker"=>"Picus canus", "Lesser Short-Toed Lark"=>"Calandrella rufescens", "Eurasian Crag Martin"=>"Hirundo rupestris", "Red-rumped Swallow"=>"Cecropis daurica", "Blyth`s Pipit"=>"Anthus godlewskii", "Pechora Pipit"=>"Anthus gustavi", "Grey-headed Wagtail"=>"Motacilla thunbergi", "Yellow-Headed Wagtail"=>"Motacilla lutea", "White-throated Dipper"=>"Cinclus cinclus", "Rufous-Tailed Scrub Robin"=>"Cercotrichas galactotes", "Thrush Nightingale"=>"Luscinia luscinia", "White-throated Robin"=>"Irania gutturalis", "Caspian Stonechat"=>"Saxicola maura variegata", "Western Black-eared Wheatear"=>"Oenanthe hispanica", "Rufous-tailed Rock Thrush"=>"Monticola saxatilis", "Red-throated Thrush/Black-throated"=>"Turdus ruficollis", "American Robin"=>"Turdus migratorius", "Zitting Cisticola"=>"Cisticola juncidis", "Lanceolated Warbler"=>"Locustella lanceolata", "River Warbler"=>"Locustella fluviatilis", "Blyth`s Reed Warbler"=>"Acrocephalus dumetorum", "Caspian Reed Warbler"=>"Acrocephalus fuscus", "Aquatic Warbler"=>"Acrocephalus paludicola", "Booted Warbler"=>"Acrocephalus caligatus", "Marmora's Warbler"=>"Sylvia sarda", "Dartford Warbler"=>"Sylvia undata", "Subalpine Warbler"=>"Sylvia cantillans", "Ménétries's Warbler"=>"Sylvia mystacea", "Rüppel's Warbler"=>"Sylvia rueppelli", "Asian Desert Warbler"=>"Sylvia nana", "Western Orphean Warbler"=>"Sylvia hortensis hortensis", "Arctic Warbler"=>"Phylloscopus borealis", "Radde`s Warbler"=>"Phylloscopus schwarzi", "Western Bonelli`s Warbler"=>"Phylloscopus bonelli", "Red-breasted Flycatcher"=>"Ficedula parva", "Eurasian Penduline Tit"=>"Remiz pendulinus", "Daurian Shrike"=>"Lanius isabellinus", "Long-Tailed Shrike"=>"Lanius schach", "Lesser Grey Shrike"=>"Lanius minor", "Southern Grey Shrike"=>"Lanius meridionalis", "Masked Shrike"=>"Lanius nubicus", "Spotted Nutcracker"=>"Nucifraga caryocatactes", "Daurian Jackdaw"=>"Corvus dauuricus", "Purple-Backed Starling"=>"Sturnus sturninus", "Red-Fronted Serin"=>"Serinus pusillus", "Arctic Redpoll"=>"Carduelis hornemanni", "Scottish Crossbill"=>"Loxia scotica", "Parrot Crossbill"=>"Loxia pytyopsittacus", "Black-faced Bunting"=>"Emberiza spodocephala", "Pink-footed Goose"=>"Anser brachyrhynchus", "Black-winged Kite"=>"Elanus caeruleus", "European Bee-eater"=>"Merops apiaster", "Sabine`s Gull"=>"Larus sabini", "Sooty Shearwater"=>"Puffinus griseus", "Lesser Canada Goose"=>"Branta hutchinsii", "Ring-necked Duck"=>"Aythya collaris", "Greater Flamingo"=>"Phoenicopterus roseus", "Iberian Chiffchaff"=>"Phylloscopus ibericus", "Ashy-headed Wagtail"=>"Motacilla cinereocapilla", "Stilt Sandpiper"=>"Calidris himantopus", "Siberian Stonechat"=>"Saxicola maurus", "Greater Yellowlegs"=>"Tringa melanoleuca", "Forster`s Tern"=>"Sterna forsteri", "Dusky Warbler"=>"Phylloscopus fuscatus", "Cirl Bunting"=>"Emberiza cirlus", "Olive-backed Pipit"=>"Anthus hodgsoni", "Sociable Lapwing"=>"Vanellus gregarius", "Spotted Sandpiper"=>"Actitis macularius", "Baird`s Sandpiper"=>"Calidris bairdii", "Rustic Bunting"=>"Emberiza rustica", "Yellow-browed Bunting"=>"Emberiza chrysophrys", "Great Shearwater"=>"Puffinus gravis", "Bonelli`s Eagle"=>"Aquila fasciata", "Calandra Lark"=>"Melanocorypha calandra", "Sardinian Warbler"=>"Sylvia melanocephala", "Ross's Gull"=>"Larus roseus", "Yellow-Breasted Bunting"=>"Emberiza aureola", "Pine Bunting"=>"Emberiza leucocephalos", "Black Guillemot"=>"Cepphus grylle", "Pied-billed Grebe"=>"Podilymbus podiceps", "Soft-plumaged Petrel"=>"Pterodroma mollis", "Bulwer's Petrel"=>"Bulweria bulwerii", "White-Faced Storm-Petrel"=>"Pelagodroma marina", "Pallas’s Fish Eagle"=>"Haliaeetus leucoryphus", "Sandhill Crane"=>"Grus canadensis", "Macqueen’s Bustard"=>"Chlamydotis macqueenii", "White-tailed Lapwing"=>"Vanellus leucurus", "Great Knot"=>"Calidris tenuirostris", "Semipalmated Sandpiper"=>"Calidris pusilla", "Red-necked Stint"=>"Calidris ruficollis", "Slender-billed Curlew"=>"Numenius tenuirostris", "Bridled Tern"=>"Onychoprion anaethetus", "Pallas’s Sandgrouse"=>"Syrrhaptes paradoxus", "European Scops Owl"=>"Otus scops", "Northern Hawk Owl"=>"Surnia ulula", "White-Throated Needletail"=>"Hirundapus caudacutus", "Belted Kingfisher"=>"Ceryle alcyon", "Blue-cheeked Bee-eater"=>"Merops persicus", "Black-headed Wagtail"=>"Motacilla feldegg", "Northern Mockingbird"=>"Mimus polyglottos", "Alpine Accentor"=>"Prunella collaris", "Red-flanked Bluetail"=>"Tarsiger cyanurus", "Isabelline Wheatear"=>"Oenanthe isabellina", "Pied Wheatear"=>"Oenanthe pleschanka", "Eastern Black-eared Wheatear"=>"Oenanthe melanoleuca", "Desert Wheatear"=>"Oenanthe deserti", "White`s Thrush"=>"Zoothera aurea", "Siberian Thrush"=>"Zoothera sibirica", "Eyebrowed Thrush"=>"Turdus obscurus", "Dusky Thrush"=>"Turdus eunomus", "Black-throated Thrush"=>"Turdus atrogularis", "Pallas`s Grasshopper Warbler"=>"Locustella certhiola", "Spectacled Warbler"=>"Sylvia conspicillata", "Two-barred Warbler"=>"Phylloscopus plumbeitarsus", "Eastern Bonelli’s Warbler"=>"Phylloscopus orientalis", "Collared Flycatcher"=>"Ficedula albicollis", "Wallcreeper"=>"Tichodroma muraria", "Turkestan Shrike"=>"Lanius phoenicuroides", "Steppe Grey Shrike"=>"Lanius pallidirostris", "Spanish Sparrow"=>"Passer hispaniolensis", "Red-eyed Vireo"=>"Vireo olivaceus", "Myrtle Warbler"=>"Dendroica coronata", "White-crowned Sparrow"=>"Zonotrichia leucophrys", "White-throated Sparrow"=>"Zonotrichia albicollis", "Cretzschmar`s Bunting"=>"Emberiza caesia", "Chestnut Bunting"=>"Emberiza rutila", "Red-headed Bunting"=>"Emberiza bruniceps", "Black-headed Bunting"=>"Emberiza melanocephala", "Indigo Bunting"=>"Passerina cyanea", "Balearic Woodchat Shrike"=>"Lanius senator badius", "Demoiselle Crane"=>"Grus virgo", "Chough"=>"Pyrrhocorax pyrrhocorax", "Red-Billed Chough"=>"Pyrrhocorax graculus", "Elegant Tern"=>"Sterna elegans", "Chukar"=>"Alectoris chukar", "Yellow-Billed Cuckoo"=>"Coccyzus americanus", "American Sandwich Tern"=>"Sterna sandvicensis acuflavida", "Olive-Tree Warbler"=>"Hippolais olivetorum", "Eastern Olivaceous Warbler"=>"Acrocephalus pallidus", "Indian Cormorant"=>"Phalacrocorax fuscicollis", "Spur-Winged Lapwing"=>"Vanellus spinosus", "Yelkouan Shearwater"=>"Puffinus yelkouan", "Trumpeter Finch"=>"Bucanetes githagineus", "Red Grouse"=>"Lagopus scoticus", "Rock Ptarmigan"=>"Lagopus mutus", "Long-Tailed Cormorant"=>"Phalacrocorax africanus", "Double-crested Cormorant"=>"Phalacrocorax auritus", "Magnificent Frigatebird"=>"Fregata magnificens", "Naumann's Thrush"=>"Turdus naumanni", "Oriental Pratincole"=>"Glareola maldivarum", "Bufflehead"=>"Bucephala albeola", "Snowfinch"=>"Montifrigilla nivalis", "Ural owl"=>"Strix uralensis", "Spanish Wagtail"=>"Motacilla iberiae", "Song Sparrow"=>"Melospiza melodia", "Rock Bunting"=>"Emberiza cia", "Siberian Rubythroat"=>"Luscinia calliope", "Pallid Swift"=>"Apus pallidus", "Eurasian Pygmy Owl"=>"Glaucidium passerinum", "Madeira Little Shearwater"=>"Puffinus baroli", "House Finch"=>"Carpodacus mexicanus", "Green Heron"=>"Butorides virescens", "Solitary Sandpiper"=>"Tringa solitaria", "Heuglin's Gull"=>"Larus heuglini" ); $result = array(); foreach ($items as $key=>$value) { if (strpos(strtolower($key), $q) !== false) { array_push($result, array("id"=>$value, "label"=>$key, "value" => strip_tags($key))); } if (count($result) > 11) break; } // json_encode is available in PHP 5.2 and above, or you can install a PECL module in earlier versions echo json_encode($result); ?> ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/autocomplete/xml.html ================================================ jQuery UI Autocomplete - XML data parsed once
      Result:

      This demo shows how to retrieve some XML data, parse it using jQuery's methods, then provide it to the autocomplete as the datasource.

      This should also serve as a reference on how to parse a remote XML datasource - the parsing would just happen for each request within the source-callback.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/button/checkbox.html ================================================ jQuery UI Button - Checkboxes

      A checkbox is styled as a toggle button with the button widget. The label element associated with the checkbox is used for the button text.

      This demo also demonstrates three checkboxes styled as a button set by calling .buttonset() on a common container.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/button/default.html ================================================ jQuery UI Button - Default functionality
      An anchor

      Examples of the markup that can be used for buttons: A button element, an input of type submit and an anchor.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/button/icons.html ================================================ jQuery UI Button - Icons

      Some buttons with various combinations of text and icons, here specified via metadata.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/button/index.html ================================================ jQuery UI Button Demos ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/button/radio.html ================================================ jQuery UI Button - Radios

      A set of three radio buttons transformed into a button set.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/button/splitbutton.html ================================================ jQuery UI Button - Split button

      An example of a split button built with two buttons: A plain button with just text, one with only a primary icon and no text. Both are grouped together in a set.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/button/toolbar.html ================================================ jQuery UI Button - Toolbar

      A mediaplayer toolbar. Take a look at the underlying markup: A few button elements, an input of type checkbox for the Shuffle button, and three inputs of type radio for the Repeat options.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/datepicker/alt-field.html ================================================ jQuery UI Datepicker - Populate alternate field

      Date:  

      Populate an alternate field with its own date format whenever a date is selected using the altField and altFormat options. This feature could be used to present a human-friendly date for user selection, while passing a more computer-friendly date through for further processing.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/datepicker/animation.html ================================================ jQuery UI Datepicker - Animations

      Date:

      Animations:

      Use different animations when opening or closing the datepicker. Choose an animation from the dropdown, then click on the input to see its effect. You can use one of the three standard animations or any of the UI Effects.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/datepicker/buttonbar.html ================================================ jQuery UI Datepicker - Display button bar

      Date:

      Display a button for selecting Today's date and a Done button for closing the calendar with the boolean showButtonPanel option. Each button is enabled by default when the bar is displayed, but can be turned off with additional options. Button text is customizable.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/datepicker/date-formats.html ================================================ jQuery UI Datepicker - Format date

      Date:

      Format options:

      Display date feedback in a variety of ways. Choose a date format from the dropdown, then click on the input and select a date to see it in that format.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/datepicker/date-range.html ================================================ jQuery UI Datepicker - Select a Date Range

      Select the date range to search for.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/datepicker/default.html ================================================ jQuery UI Datepicker - Default functionality

      Date:

      The datepicker is tied to a standard form input field. Focus on the input (click, or use the tab key) to open an interactive calendar in a small overlay. Choose a date, click elsewhere on the page (blur the input), or hit the Esc key to close. If a date is chosen, feedback is shown as the input's value.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/datepicker/dropdown-month-year.html ================================================ jQuery UI Datepicker - Display month & year menus

      Date:

      Show month and year dropdowns in place of the static month/year header to facilitate navigation through large timeframes. Add the boolean changeMonth and changeYear options.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/datepicker/icon-trigger.html ================================================ jQuery UI Datepicker - Icon trigger

      Date:

      Click the icon next to the input field to show the datepicker. Set the datepicker to open on focus (default behavior), on icon click, or both.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/datepicker/index.html ================================================ jQuery UI Datepicker Demos ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/datepicker/inline.html ================================================ jQuery UI Datepicker - Display inline
      Date:

      Display the datepicker embedded in the page instead of in an overlay. Simply call .datepicker() on a div instead of an input.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/datepicker/localization.html ================================================ jQuery UI Datepicker - Localize calendar

      Date:  

      Localize the datepicker calendar language and format (English / Western formatting is the default). The datepicker includes built-in support for languages that read right-to-left, such as Arabic and Hebrew.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/datepicker/min-max.html ================================================ jQuery UI Datepicker - Restrict date range

      Date:

      Restrict the range of selectable dates with the minDate and maxDate options. Set the beginning and end dates as actual dates (new Date(2009, 1 - 1, 26)), as a numeric offset from today (-20), or as a string of periods and units ('+1M +10D'). For the last, use 'D' for days, 'W' for weeks, 'M' for months, or 'Y' for years.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/datepicker/multiple-calendars.html ================================================ jQuery UI Datepicker - Display multiple months

      Date:

      Set the numberOfMonths option to an integer of 2 or more to show multiple months in a single datepicker.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/datepicker/other-months.html ================================================ jQuery UI Datepicker - Dates in other months

      Date:

      The datepicker can show dates that come from other than the main month being displayed. These other dates can also be made selectable.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/datepicker/show-week.html ================================================ jQuery UI Datepicker - Show week of the year

      Date:

      The datepicker can show the week of the year. The default calculation follows the ISO 8601 definition: the week starts on Monday, the first week of the year contains the first Thursday of the year. This means that some days from one year may be placed into weeks 'belonging' to another year.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/demos.css ================================================ body { font-size: 62.5%; } table { font-size: 1em; } /* Site -------------------------------- */ body { font-family: "Trebuchet MS", "Helvetica", "Arial", "Verdana", "sans-serif"; } /* Layout -------------------------------- */ .layout-grid { width: 960px; border-spacing: 0; border-collapse: collapse; } .layout-grid td { vertical-align: top; } .layout-grid td.left-nav { width: 140px; } .layout-grid td.normal { border-left: 1px solid #eee; padding: 20px 24px; font-family: "Trebuchet MS", "Helvetica", "Arial", "Verdana", "sans-serif"; } .layout-grid td.demos { background: url('/images/demos_bg.jpg') no-repeat; height: 337px; overflow: hidden; } /* Normal -------------------------------- */ .normal h3, .normal h4 { margin: 0; font-weight: normal; } .normal h3 { padding: 0 0 9px; font-size: 1.8em; } .normal h4 { padding-bottom: 21px; border-bottom: 1px dashed #999; font-size: 1.2em; font-weight: bold; } .normal p { font-size: 1.2em; } /* Demos */ .demos-nav, .demos-nav dt, .demos-nav dd, .demos-nav ul, .demos-nav li { margin: 0; padding: 0 } .demos-nav { float: left; width: 170px; font-size: 1.3em; } .demos-nav dt, .demos-nav h4 { margin: 0; padding: 0; font: normal 1.1em "Trebuchet MS", "Helvetica", "Arial", "Verdana", "sans-serif"; color: #e87b10; } .demos-nav dt, .demos-nav h4 { margin-top: 1.5em; margin-bottom: 0; padding-left: 8px; padding-bottom:5px; line-height: 1.2em; border-bottom: 1px solid #F4F4F4; } .demos-nav dd a, .demos-nav li a { border-bottom: 1px solid #F4F4F4; display:block; padding: 4px 3px 4px 8px; font-size: 90%; text-decoration: none; color: #555 ; margin:2px 0; height:13px; } .demos-nav dd a:hover, .demos-nav dd a:focus, .demos-nav dd a:hover, .demos-nav dd a:focus { background: #f3f3f3; color:#000; -moz-border-radius: 5px; -webkit-border-radius: 5px; } .demos-nav dd a.selected { background: #555; color:#ffffff; -moz-border-radius: 5px; -webkit-border-radius: 5px; } /* new styles for demo pages, added by Filament 12.29.08 eventually we should convert the font sizes to ems -- using px for now to minimize style conflicts */ .normal h3.demo-header { font-size:32px; padding:0 0 5px; border-bottom:1px solid #eee; text-transform: capitalize; } .normal h4.demo-subheader { font-size:10px; text-transform: uppercase; color:#999; padding:8px 0 3px; border:0; margin:0; } #demo-notes a, #demo-link a, #demo-source a { color:#1b75bb; text-decoration:none; } .normal a:hover, .normal a:active { color:#0b559b; } #demo-config { padding:20px 0 0; } #demo-frame { float:left; width:540px; height:380px; border:1px solid #ddd; overflow: auto; position: relative; } #demo-frame h3, #demo-frame h4 { padding: 0; font-weight: bold; font-size: 1em; } #demo-config-menu { float:right; width:180px; } #demo-config-menu h4 { font-size:13px; color:#666; font-weight:normal; border:0; padding-left:18px; } #demo-config-menu ul { list-style: none; padding: 0; margin: 0; } #demo-config-menu li { font-size:12px; padding:0 0 0 10px; margin:3px 0; zoom: 1; } #demo-config-menu li a:link, #demo-config-menu li a:visited { display:block; padding:1px 8px 4px; border-bottom:1px dotted #b3b3b3; } * html #demo-config-menu li a:link, * html #demo-config-menu li a:visited { padding:1px 8px 2px; } #demo-config-menu li a:hover, #demo-config-menu li a:active { background-color:#f6f6f6; } #demo-config-menu li.demo-config-on { background: url(images/demo-config-on-tile.gif) repeat-x left center; } #demo-config-menu li.demo-config-on a:link, #demo-config-menu li.demo-config-on a:visited, #demo-config-menu li.demo-config-on a:hover, #demo-config-menu li.demo-config-on a:active { background: url(images/demo-config-on.gif) no-repeat left; padding-left:18px; color:#fff; border:0; margin-left:-10px; margin-top: 0px; margin-bottom: 0px; } #demo-source, #demo-notes { clear: both; padding: 20px 0 0; font-size: 1.3em; } #demo-notes { width:520px; color:#333; font-size: 1em; } #demo-notes p code, .demo-description p code { padding: 0; font-weight: bold; } #demo-source pre, #demo-source code { padding: 0; } code, pre { padding:8px 0 8px 20px ; font-size: 1.2em; line-height:130%; } #demo-source a:link, #demo-source a:visited, #demo-source a:hover, #demo-source a:active { font-size:12px; padding-left:13px; background-position: left center; background-repeat: no-repeat; } #demo-source a.source-open:link, #demo-source a.source-open:visited, #demo-source a.source-open:hover, #demo-source a.source-open:active { background-image: url(images/demo-spindown-open.gif); } #demo-source a.source-closed:link, #demo-source a.source-closed:visited, #demo-source a.source-closed:hover, #demo-source a.source-closed:active { background-image: url(images/demo-spindown-closed.gif); } div.demo { padding:12px; font-family: "Trebuchet MS", "Arial", "Helvetica", "Verdana", "sans-serif"; } div.demo h3.docs { clear:left; font-size:12px; font-weight:normal; padding:0 0 1em; margin:0; } div.demo-description { clear:both; padding:12px; font-family: "Trebuchet MS", "Arial", "Helvetica", "Verdana", "sans-serif"; font-size: 1.3em; line-height: 1.4em; } .ui-draggable, .ui-droppable { background-position: top left; } .left-nav .demos-nav { padding-right: 10px; } #demo-link { font-size:11px; padding-top: 6px; clear: both; overflow: hidden; } #demo-link a span.ui-icon { float:left; margin-right:3px; } /* Component containers ----------------------------------*/ #widget-docs .ui-widget { font-family: Trebuchet MS,Verdana,Arial,sans-serif; font-size: 1em; } #widget-docs .ui-widget input, #widget-docs .ui-widget select, #widget-docs .ui-widget textarea, #widget-docs .ui-widget button { font-family: Trebuchet MS,Verdana,Arial,sans-serif; font-size: 1em; } #widget-docs .ui-widget-header { border: 1px solid #ffffff; background: #464646 url(images/464646_40x100_textures_01_flat_100.png) 50% 50% repeat-x; color: #ffffff; font-weight: bold; } #widget-docs .ui-widget-header a { color: #ffffff; } #widget-docs .ui-widget-content { border: 1px solid #ffffff; background: #ffffff url(images/ffffff_40x100_textures_01_flat_75.png) 50% 50% repeat-x; color: #222222; } #widget-docs .ui-widget-content a { color: #222222; } /* Interaction states ----------------------------------*/ #widget-docs .ui-state-default, #widget-docs .ui-widget-content #widget-docs .ui-state-default { border: 1px solid #666666; background: #555555 url(images/555555_40x100_textures_03_highlight_soft_75.png) 50% 50% repeat-x; font-weight: normal; color: #ffffff; outline: none; } #widget-docs .ui-state-default a { color: #ffffff; text-decoration: none; outline: none; } #widget-docs .ui-state-hover, #widget-docs .ui-widget-content #widget-docs .ui-state-hover, #widget-docs .ui-state-focus, #widget-docs .ui-widget-content #widget-docs .ui-state-focus { border: 1px solid #666666; background: #444444 url(images/444444_40x100_textures_03_highlight_soft_60.png) 50% 50% repeat-x; font-weight: normal; color: #ffffff; outline: none; } #widget-docs .ui-state-hover a { color: #ffffff; text-decoration: none; outline: none; } #widget-docs .ui-state-active, #widget-docs .ui-widget-content #widget-docs .ui-state-active { border: 1px solid #666666; background: #ffffff url(images/ffffff_40x100_textures_01_flat_65.png) 50% 50% repeat-x; font-weight: normal; color: #F6921E; outline: none; } #widget-docs .ui-state-active a { color: #F6921E; outline: none; text-decoration: none; } /* Interaction Cues ----------------------------------*/ #widget-docs .ui-state-highlight, #widget-docs .ui-widget-content #widget-docs .ui-state-highlight {border: 1px solid #fcefa1; background: #fbf9ee url(images/fbf9ee_40x100_textures_02_glass_55.png) 50% 50% repeat-x; color: #363636; } #widget-docs .ui-state-error, #widget-docs .ui-widget-content #widget-docs .ui-state-error {border: 1px solid #cd0a0a; background: #fef1ec url(images/fef1ec_40x100_textures_05_inset_soft_95.png) 50% bottom repeat-x; color: #cd0a0a; } #widget-docs .ui-state-error-text, #widget-docs .ui-widget-content #widget-docs .ui-state-error-text { color: #cd0a0a; } #widget-docs .ui-state-disabled, #widget-docs .ui-widget-content #widget-docs .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; } #widget-docs .ui-priority-primary, #widget-docs .ui-widget-content #widget-docs .ui-priority-primary { font-weight: bold; } #widget-docs .ui-priority-secondary, #widget-docs .ui-widget-content #widget-docs .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; } /* Icons ----------------------------------*/ /* states and images */ #demo-frame-wrapper .ui-icon, #widget-docs .ui-icon { width: 16px; height: 16px; background-image: url(images/222222_256x240_icons_icons.png); } #widget-docs .ui-widget-content .ui-icon {background-image: url(images/222222_256x240_icons_icons.png); } #widget-docs .ui-widget-header .ui-icon {background-image: url(images/222222_256x240_icons_icons.png); } #widget-docs .ui-state-default .ui-icon { background-image: url(images/888888_256x240_icons_icons.png); } #widget-docs .ui-state-hover .ui-icon, #widget-docs .ui-state-focus .ui-icon {background-image: url(images/454545_256x240_icons_icons.png); } #widget-docs .ui-state-active .ui-icon {background-image: url(images/454545_256x240_icons_icons.png); } #widget-docs .ui-state-highlight .ui-icon {background-image: url(images/2e83ff_256x240_icons_icons.png); } #widget-docs .ui-state-error .ui-icon, #widget-docs .ui-state-error-text .ui-icon {background-image: url(images/cd0a0a_256x240_icons_icons.png); } /* Misc visuals ----------------------------------*/ /* Corner radius */ #widget-docs .ui-corner-tl { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; } #widget-docs .ui-corner-tr { -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; } #widget-docs .ui-corner-bl { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; } #widget-docs .ui-corner-br { -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; } #widget-docs .ui-corner-top { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; } #widget-docs .ui-corner-bottom { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; } #widget-docs .ui-corner-right { -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; } #widget-docs .ui-corner-left { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; } #widget-docs .ui-corner-all { -moz-border-radius: 4px; -webkit-border-radius: 4px; } /* Overlays */ #widget-docs .ui-widget-overlay { background: #aaaaaa url(images/aaaaaa_40x100_textures_01_flat_0.png) 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); } #widget-docs .ui-widget-shadow { margin: -8px 0 0 -8px; padding: 8px; background: #aaaaaa url(images/aaaaaa_40x100_textures_01_flat_0.png) 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); -moz-border-radius: 8px; -webkit-border-radius: 8px; } /* ----------------------------------*/ #widget-docs { margin:20px 0 0; border: none; } #widget-docs h2, #widget-docs h3, #widget-docs h4, #widget-docs p, #widget-docs ul, #widget-docs code { margin:0; padding:0; } #widget-docs code { display:block; color:#444; font-size:.9em; margin:0 0 1em; } #widget-docs code strong { color:#000; } #widget-docs p { margin:0 3em 1.2em 0; } #widget-docs p.intro { font-size:13px; color:#666; line-height:1.3; } #widget-docs ul { list-style-type: none; } #widget-docs h2 { font-size:16px; margin:1.2em 0 .5em; } #widget-docs h3 { font-size:14px; color:#e6820E; margin:1.5em 0 .5em; } .normal #widget-docs h4 { font-size:12px; color:#000; border:0; margin:0 0 .5em; } #docs-overview-main { width:400px; } #docs-overview-sidebar { float:right; width:200px; } #docs-overview-sidebar a span { color:#666; } #widget-docs #docs-overview-main p { margin-right:0; } #widget-docs #docs-overview-sidebar h4 { padding-left:0; } .docs-list-header { float:left; width:100%; margin:10px 0 0; border-bottom:1px solid #eee; } #widget-docs .docs-list-header h2 { float:left; margin:0; } #widget-docs .docs-list-header p { float:right; margin:5px 0; font-size:11px; } .docs-list { float:left; width:100%; padding:0 0 10px; } .docs-list .param-header { float:left; clear:left; width:100%; padding:8px 0; border-top:1px solid #eee; } #widget-docs .param-header h3, #widget-docs .param-header p { margin:0; float:left; } #widget-docs .param-header h3 { width:50%; } #widget-docs .param-header h3 span { background: url(images/demo-spindown-closed.gif) no-repeat left; padding-left:13px; } #widget-docs .param-open .param-header h3 span { background: url(images/demo-spindown-open.gif) no-repeat left; } #widget-docs .param-header p { width:24%; } #widget-docs .param-header p.param-type span { background: url(images/icon-docs-info.gif) no-repeat left; cursor:pointer; border-bottom:1px dashed #ccc; padding-left:15px; } .param-details { padding-left:13px; } .param-args { margin:0 0 1.5em; border-top:1px dotted #ccc;} .param-args td { padding:3px 30px 3px 5px; border-bottom:1px dotted #ccc; } /* overrides for ui-tab styles */ #widget-docs ul.ui-tabs-nav { padding:0 0 0 8px; } #widget-docs .ui-tabs-nav li { margin:5px 5px 0 0; } #widget-docs .ui-tabs-nav li a:link, #widget-docs .ui-tabs-nav li a:visited, #widget-docs .ui-tabs-nav li a:hover, #widget-docs .ui-tabs-nav li a:active { font-size:14px; padding:4px 1.2em 3px; color:#fff; } #widget-docs .ui-tabs-nav li.ui-tabs-active a:link, #widget-docs .ui-tabs-nav li.ui-tabs-active a:visited, #widget-docs .ui-tabs-nav li.ui-tabs-active a:hover, #widget-docs .ui-tabs-nav li.ui-tabs-active a:active { color:#e6820E; } #widget-docs .ui-tabs-panel { padding:20px 9px; font-size:12px; line-height:1.4; color:#000; } #widget-docs .ui-widget-content a:link, #widget-docs .ui-widget-content a:visited { color:#1b75bb; text-decoration:none; } #widget-docs .ui-widget-content a:hover, #widget-docs .ui-widget-content a:active { color:#0b559b; } ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/dialog/animated.html ================================================ jQuery UI Dialog - Animation

      This is an animated dialog which is useful for displaying information. The dialog window can be moved, resized and closed with the 'x' icon.

      Dialogs may be animated by specifying an effect for the show and/or hide properties. You must include the individual effects file for any effects you would like to use.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/dialog/default.html ================================================ jQuery UI Dialog - Default functionality

      This is the default dialog which is useful for displaying information. The dialog window can be moved, resized and closed with the 'x' icon.

      Sed vel diam id libero rutrum convallis. Donec aliquet leo vel magna. Phasellus rhoncus faucibus ante. Etiam bibendum, enim faucibus aliquet rhoncus, arcu felis ultricies neque, sit amet auctor elit eros a lectus.


      checkbox
      radio



      The basic dialog window is an overlay positioned within the viewport and is protected from page content (like select elements) shining through with an iframe. It has a title bar and a content area, and can be moved, resized and closed with the 'x' icon by default.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/dialog/index.html ================================================ jQuery UI Dialog Demos ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/dialog/modal-confirmation.html ================================================ jQuery UI Dialog - Modal confirmation

      These items will be permanently deleted and cannot be recovered. Are you sure?

      Sed vel diam id libero rutrum convallis. Donec aliquet leo vel magna. Phasellus rhoncus faucibus ante. Etiam bibendum, enim faucibus aliquet rhoncus, arcu felis ultricies neque, sit amet auctor elit eros a lectus.


      checkbox
      radio



      Confirm an action that may be destructive or important. Set the modal option to true, and specify primary and secondary user actions with the buttons option.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/dialog/modal-form.html ================================================ jQuery UI Dialog - Modal form

      All form fields are required.

      Existing Users:

      Name Email Password
      John Doe john.doe@example.com johndoe1

      Use a modal dialog to require that the user enter data during a multi-step process. Embed form markup in the content area, set the modal option to true, and specify primary and secondary user actions with the buttons option.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/dialog/modal-message.html ================================================ jQuery UI Dialog - Modal message

      Your files have downloaded successfully into the My Downloads folder.

      Currently using 36% of your storage space.

      Sed vel diam id libero rutrum convallis. Donec aliquet leo vel magna. Phasellus rhoncus faucibus ante. Etiam bibendum, enim faucibus aliquet rhoncus, arcu felis ultricies neque, sit amet auctor elit eros a lectus.


      checkbox
      radio



      Use a modal dialog to explicitly acknowledge information or an action before continuing their work. Set the modal option to true, and specify a primary action (Ok) with the buttons option.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/dialog/modal.html ================================================ jQuery UI Dialog - Basic modal

      Adding the modal overlay screen makes the dialog look more prominent because it dims out the page content.

      Sed vel diam id libero rutrum convallis. Donec aliquet leo vel magna. Phasellus rhoncus faucibus ante. Etiam bibendum, enim faucibus aliquet rhoncus, arcu felis ultricies neque, sit amet auctor elit eros a lectus.


      checkbox
      radio



      A modal dialog prevents the user from interacting with the rest of the page until it is closed.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/draggable/constrain-movement.html ================================================ jQuery UI Draggable - Constrain movement

      Constrain movement along an axis:

      I can be dragged only vertically

      I can be dragged only horizontally

      Or to within another DOM element:

      I'm contained within the box

      I'm contained within the box's parent

      I'm contained within my parent

      Constrain the movement of each draggable by defining the boundaries of the draggable area. Set the axis option to limit the draggable's path to the x- or y-axis, or use the containment option to specify a parent DOM element or a jQuery selector, like 'document.'

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/draggable/cursor-style.html ================================================ jQuery UI Draggable - Cursor style

      I will always stick to the center (relative to the mouse)

      My cursor is at left -5 and top -5

      My cursor position is only controlled for the 'bottom' value

      Position the cursor while dragging the object. By default the cursor appears in the center of the dragged object; use the cursorAt option to specify another location relative to the draggable (specify a pixel value from the top, right, bottom, and/or left). Customize the cursor's appearance by supplying the cursor option with a valid CSS cursor value: default, move, pointer, crosshair, etc.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/draggable/default.html ================================================ jQuery UI Draggable - Default functionality

      Drag me around

      Enable draggable functionality on any DOM element. Move the draggable object by clicking on it with the mouse and dragging it anywhere within the viewport.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/draggable/delay-start.html ================================================ jQuery UI Draggable - Delay start

      Only if you drag me by 20 pixels, the dragging will start

      Regardless of the distance, you have to drag and wait for 1000ms before dragging starts

      Delay the start of dragging for a number of milliseconds with the delay option; prevent dragging until the cursor is held down and dragged a specifed number of pixels with the distance option.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/draggable/events.html ================================================ jQuery UI Draggable - Events

      Drag me to trigger the chain of events.

      • "start" invoked 0x
      • "drag" invoked 0x
      • "stop" invoked 0x

      Layer functionality onto the draggable using the start, drag, and stop events. Start is fired at the start of the drag; drag during the drag; and stop when dragging stops.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/draggable/handle.html ================================================ jQuery UI Draggable - Handles

      I can be dragged only by this handle

      You can drag me around…

      …but you can't drag me by this handle.

      Allow dragging only when the cursor is over a specific part of the draggable. Use the handle option to specify the jQuery selector of an element (or group of elements) used to drag the object.

      Or prevent dragging when the cursor is over a specific element (or group of elements) within the draggable. Use the cancel option to specify a jQuery selector over which to "cancel" draggable functionality.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/draggable/index.html ================================================ jQuery UI Draggable Demos ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/draggable/revert.html ================================================ jQuery UI Draggable - Revert position

      Revert the original

      Revert the helper

      Return the draggable (or it's helper) to its original location when dragging stops with the boolean revert option.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/draggable/scroll.html ================================================ jQuery UI Draggable - Auto-scroll

      Scroll set to true, default settings

      scrollSensitivity set to 100

      scrollSpeed set to 100

      Automatically scroll the document when the draggable is moved beyond the viewport. Set the scroll option to true to enable auto-scrolling, and fine-tune when scrolling is triggered and its speed with the scrollSensitivity and scrollSpeed options.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/draggable/snap-to.html ================================================ jQuery UI Draggable - Snap to element or grid

      I'm a snap target


      Default (snap: true), snaps to all other draggable elements

      I only snap to the big box

      I only snap to the outer edges of the big box

      I snap to a 20 x 20 grid

      I snap to a 80 x 80 grid

      Snap the draggable to the inner or outer boundaries of a DOM element. Use the snap, snapMode (inner, outer, both), and snapTolerance (distance in pixels the draggable must be from the element when snapping is invoked) options.

      Or snap the draggable to a grid. Set the dimensions of grid cells (height and width in pixels) with the grid option.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/draggable/sortable.html ================================================ jQuery UI Draggable + Sortable
      • Drag me down
      • Item 1
      • Item 2
      • Item 3
      • Item 4
      • Item 5

      Draggables are built to interact seamlessly with sortables.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/draggable/visual-feedback.html ================================================ jQuery UI Draggable - Visual feedback

      With helpers:

      Original

      Semi-transparent clone

      Custom helper (in combination with cursorAt)

      Stacked:

      We are draggables..

      ..whose z-indexes are controlled automatically..

      ..with the stack option.

      Provide feedback to users as they drag an object in the form of a helper. The helper option accepts the values 'original' (the draggable object moves with the cursor), 'clone' (a duplicate of the draggable moves with the cursor), or a function that returns a DOM element (that element is shown near the cursor during drag). Control the helper's transparency with the opacity option.

      To clarify which draggable is in play, bring the draggable in motion to front. Use the zIndex option to set a higher z-index for the helper, if in play, or use the stack option to ensure that the last item dragged will appear on top of others in the same group on drag stop.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/droppable/accepted-elements.html ================================================ jQuery UI Droppable - Accept

      I'm draggable but can't be dropped

      Drag me to my target

      accept: '#draggable'

      Specify using the accept option which element (or group of elements) is accepted by the target droppable.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/droppable/default.html ================================================ jQuery UI Droppable - Default functionality

      Drag me to my target

      Drop here

      Enable any DOM element to be droppable, a target for draggable elements.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/droppable/index.html ================================================ jQuery UI Droppable Demos ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/droppable/photo-manager.html ================================================ jQuery UI Droppable - Simple photo manager

      Trash Trash

      You can delete an image either by dragging it to the Trash or by clicking the trash icon.

      You can "recycle" an image by dragging it back to the gallery or by clicking the recycle icon.

      You can view larger image by clicking the zoom icon. jQuery UI dialog widget is used for the modal window.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/droppable/propagation.html ================================================ jQuery UI Droppable - Prevent propagation

      Drag me to my target

      Outer droppable

      Inner droppable (not greedy)

      Outer droppable

      Inner droppable (greedy)

      When working with nested droppables — for example, you may have an editable directory structure displayed as a tree, with folder and document nodes — the greedy option set to true prevents event propagation when a draggable is dropped on a child node (droppable).

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/droppable/revert.html ================================================ jQuery UI Droppable - Revert draggable position

      I revert when I'm dropped

      I revert when I'm not dropped

      Drop me here

      Return the draggable (or it's helper) to its original location when dragging stops with the boolean revert option set on the draggable.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/droppable/shopping-cart.html ================================================ jQuery UI Droppable - Shopping Cart Demo

      Products

      T-Shirts

      • Lolcat Shirt
      • Cheezeburger Shirt
      • Buckit Shirt

      Bags

      • Zebra Striped
      • Black Leather
      • Alligator Leather

      Gadgets

      • iPhone
      • iPod
      • iPad

      Shopping Cart

      1. Add your items here

      Demonstrate how to use an accordion to structure products into a catalog and make use drag and drop for adding them to a shopping cart, where they are sortable.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/droppable/visual-feedback.html ================================================ jQuery UI Droppable - Visual feedback

      Feedback on hover:

      Drag me to my target

      Drop here

      Feedback on activating draggable:

      Drag me to my target

      Drop here

      Change the droppable's appearance on hover, or when the droppable is active (an acceptable draggable is dropped on it). Use the hoverClass or activeClass options to specify respective classes.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/effect/default.html ================================================ jQuery UI Effects - Effect demo

      Effect

      Etiam libero neque, luctus a, eleifend nec, semper at, lorem. Sed pede. Nulla lorem metus, adipiscing ut, luctus sed, hendrerit vitae, mi.

      Run Effect

      Click the button above to show the effect.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/effect/easing.html ================================================ jQuery UI Effects - Easing demo

      All easings provided by jQuery UI are drawn above, using a HTML canvas element. Click a diagram to see the easing in action.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/effect/index.html ================================================ jQuery UI Effects Demos ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/hide/default.html ================================================ jQuery UI Effects - Hide Demo

      Hide

      Etiam libero neque, luctus a, eleifend nec, semper at, lorem. Sed pede. Nulla lorem metus, adipiscing ut, luctus sed, hendrerit vitae, mi.

      Run Effect

      Click the button above to preview the effect.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/hide/index.html ================================================ jQuery UI Effects Demos ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/index.html ================================================ jQuery UI Demos
      Interactions
      Draggable
      Droppable
      Resizable
      Selectable
      Sortable
      Widgets
      Accordion
      Autocomplete
      Button
      Datepicker
      Dialog
      Menu
      Progressbar
      Slider
      Spinner
      Tabs
      Tooltip
      Effects
      Color Animation
      Toggle Class
      Add Class
      Remove Class
      Switch Class
      Effect
      Toggle
      Hide
      Show
      Utilities
      Position
      Widget
      About jQuery UI
      Getting Started
      Upgrade Guide
      Changelog
      Roadmap
      Subversion Access
      UI Developer Guidelines
      Theming
      Theming jQuery UI
      jQuery UI CSS Framework
      ThemeRoller application
      Theme Switcher Widget

      Instructions

      These demos showcase some common uses of each jQuery UI plugin. Simply copy and paste code from the demos to get started. Have fun playing with them.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/menu/default.html ================================================ jQuery UI Menu - Default demo

      A menu with the default configuration. A list is transformed, adding themeing, mouse and keyboard navigation support. Try to tab to the menu and use the cursor keys to navigate.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/menu/index.html ================================================ jQuery UI Menu Demos ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/menu/navigationmenu.html ================================================ jQuery UI Menu - Navigation Menu demo

      A navigation menu. A list is transformed, adding themeing, mouse and keyboard navigation support. Try to tab to the menu and use the cursor keys to navigate.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/menu/topalignmenu.html ================================================ Menu Demo: Top-aligned Menu ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/position/cycler.html ================================================ jQuery UI Position - Default functionality

      A prototype for the Photoviewer using Position to place images at the center, left and right and cycle them.
      Use the links at the top to cycle, or click on the images on the left and right.
      Note how the images are repositioned when resizing the window.
      Warning: Doesn't currently work inside the demo viewer; open in a new window instead!

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/position/default.html ================================================ jQuery UI Position - Default functionality

      This is the position parent element.

      to position

      to position 2

      position...
      my:
      at:
      offset:
      collision:

      Use the form controls to configure the positioning, or drag the positioned element to modify its offset.
      Drag around the parent element to see collision detection in action.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/position/index.html ================================================ jQuery UI Position Demo ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/progressbar/animated.html ================================================ jQuery UI Progressbar - Animated

      This progressbar has an animated fill by setting the background-image on the .ui-progressbar-value element, using css.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/progressbar/default.html ================================================ jQuery UI Progressbar - Default functionality

      Default determinate progress bar.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/progressbar/index.html ================================================ jQuery UI Progressbar Demos ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/progressbar/resize.html ================================================ jQuery UI Progressbar - Resizable

      The progress bar's widths are specified in percentages for flexible sizing so it will resize to fit its container. Try resizing the height and width of this bar to see how it maintains the correct proportions. (This is not necessarily a real-world example, but it's a good illustration of how flexibly all the plugins are coded.)

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/removeClass/default.html ================================================ jQuery UI Effects - removeClass Demo
      Etiam libero neque, luctus a, eleifend nec, semper at, lorem. Sed pede.
      Run Effect

      Click the button above to preview the effect.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/removeClass/index.html ================================================ jQuery UI Effects Demos ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/resizable/animate.html ================================================ jQuery UI Resizable - Animate

      Animate

      Animate the resize action using the animate option (boolean). When this option is set to true, drag the outline to the desired location; the element animates to that size on drag stop.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/resizable/aspect-ratio.html ================================================ jQuery UI Resizable - Preserve aspect ratio

      Preserve aspect ratio

      Maintain the existing aspect ratio or set a new one to constrain the proportions on resize. Set the aspectRatio option to true, and optionally pass in a new ratio (i.e., 4/3)

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/resizable/constrain-area.html ================================================ jQuery UI Resizable - Constrain resize area

      Containment

      Resizable

      Define the boundaries of the resizable area. Use the containment option to specify a parent DOM element or a jQuery selector, like 'document.'

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/resizable/default.html ================================================ jQuery UI Resizable - Default functionality

      Resizable

      Enable any DOM element to be resizable. With the cursor grab the right or bottom border and drag to the desired width or height.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/resizable/delay-start.html ================================================ jQuery UI Resizable - Delay start

      Time delay (ms):

      Time

      Distance delay (px):

      Distance

      Delay the start of resizng for a number of milliseconds with the delay option; prevent resizing until the cursor is held down and dragged a specifed number of pixels with the distance option.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/resizable/helper.html ================================================ jQuery UI Resizable - Helper

      Helper

      Display only an outline of the element while resizing by setting the helper option to a CSS class.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/resizable/index.html ================================================ jQuery UI Resizable Demos ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/resizable/max-min.html ================================================ jQuery UI Resizable - Maximum / minimum size

      Resize larger / smaller

      Limit the resizable element to a maximum or minimum height or width using the maxHeight, maxWidth, minHeight, and minWidth options.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/resizable/snap-to-grid.html ================================================ jQuery UI Resizable - Snap to grid

      Grid

      Snap the resizable element to a grid. Set the dimensions of grid cells (height and width in pixels) with the grid option.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/resizable/synchronous-resize.html ================================================ jQuery UI Resizable - Synchronous resize

      Resize

      will also resize

      Resize multiple elements simultaneously by clicking and dragging the sides of one. Pass a shared selector into the alsoResize option.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/resizable/textarea.html ================================================ jQuery UI Resizable - Textarea

      Display only an outline of the element while resizing by setting the helper option to a CSS class.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/resizable/visual-feedback.html ================================================ jQuery UI Resizable - Visual feedback

      Ghost

      Instead of showing the actual element during resize, set the ghost option to true to show a semi-transparent part of the element.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/selectable/default.html ================================================ jQuery UI Selectable - Default functionality
      1. Item 1
      2. Item 2
      3. Item 3
      4. Item 4
      5. Item 5
      6. Item 6
      7. Item 7

      Enable a DOM element (or group of elements) to be selectable. Draw a box with your cursor to select items. Hold down the Ctrl key to make multiple non-adjacent selections.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/selectable/display-grid.html ================================================ jQuery UI Selectable - Display as grid
      1. 1
      2. 2
      3. 3
      4. 4
      5. 5
      6. 6
      7. 7
      8. 8
      9. 9
      10. 10
      11. 11
      12. 12

      To arrange selectable items as a grid, give them identical dimensions and float them using CSS.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/selectable/index.html ================================================ jQuery UI Selectable Demos ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/selectable/serialize.html ================================================ jQuery UI Selectable - Serialize

      You've selected: none.

      1. Item 1
      2. Item 2
      3. Item 3
      4. Item 4
      5. Item 5
      6. Item 6

      Write a function that fires on the stop event to collect the index values of selected items. Present values as feedback, or pass as a data string.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/show/default.html ================================================ jQuery UI Effects - Show Demo

      Show

      Etiam libero neque, luctus a, eleifend nec, semper at, lorem. Sed pede. Nulla lorem metus, adipiscing ut, luctus sed, hendrerit vitae, mi.

      Run Effect

      Click the button above to preview the effect.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/show/index.html ================================================ jQuery UI Effects Demos ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/slider/colorpicker.html ================================================ jQuery UI Slider - Colorpicker

      Simple Colorpicker

      Combine three sliders to create a simple RGB colorpicker.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/slider/default.html ================================================ jQuery UI Slider - Default functionality

      The basic slider is horizontal and has a single handle that can be moved with the mouse or by using the arrow keys.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/slider/hotelrooms.html ================================================ jQuery UI Slider - Range with fixed minimum

      How to bind a slider to an existing select element. The select stays visible to display the change. When the select is changed, the slider is updated, too.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/slider/index.html ================================================ jQuery UI Slider Demos ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/slider/multiple-vertical.html ================================================ jQuery UI Slider - Multiple sliders

      Master volume

      Graphic EQ

      88 77 55 33 40 45 70

      Combine horizontal and vertical sliders, each with their own options, to create the UI for a music player.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/slider/range-vertical.html ================================================ jQuery UI Slider - Vertical range slider

      Change the orientation of the range slider to vertical. Assign a height value via .height() or by setting the height through CSS, and set the orientation option to "vertical."

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/slider/range.html ================================================ jQuery UI Slider - Range slider

      Set the range option to true to capture a range of values with two drag handles. The space between the handles is filled with a different background color to indicate those values are selected.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/slider/rangemax.html ================================================ jQuery UI Slider - Range with fixed maximum

      Fix the maximum value of the range slider so that the user can only select a minimum. Set the range option to "max."

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/slider/rangemin.html ================================================ jQuery UI Slider - Range with fixed minimum

      Fix the minimum value of the range slider so that the user can only select a maximum. Set the range option to "min."

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/slider/side-scroll.html ================================================ jQuery UI Slider - Slider scrollbar
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20

      Use a slider to manipulate the positioning of content on the page. In this case, it acts as a scrollbar with the potential to capture values if needed.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/slider/slider-vertical.html ================================================ jQuery UI Slider - Vertical slider

      Change the orientation of the slider to vertical. Assign a height value via .height() or by setting the height through CSS, and set the orientation option to "vertical."

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/slider/steps.html ================================================ jQuery UI Slider - Snap to increments

      Increment slider values with the step option set to an integer, commonly a dividend of the slider's maximum value. The default increment is 1.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/slider/tabs.html ================================================ jQuery UI Slider - Snap to increments

      Proin elit arcu, rutrum commodo, vehicula tempus, commodo a, risus. Curabitur nec arcu. Donec sollicitudin mi sit amet mauris. Nam elementum quam ullamcorper ante. Etiam aliquet massa et lorem. Mauris dapibus lacus auctor risus. Aenean tempor ullamcorper leo. Vivamus sed magna quis ligula eleifend adipiscing. Duis orci. Aliquam sodales tortor vitae ipsum. Aliquam nulla. Duis aliquam molestie erat. Ut et mauris vel pede varius sollicitudin. Sed ut dolor nec orci tincidunt interdum. Phasellus ipsum. Nunc tristique tempus lectus.

      Morbi tincidunt, dui sit amet facilisis feugiat, odio metus gravida ante, ut pharetra massa metus id nunc. Duis scelerisque molestie turpis. Sed fringilla, massa eget luctus malesuada, metus eros molestie lectus, ut tempus eros massa ut dolor. Aenean aliquet fringilla sem. Suspendisse sed ligula in ligula suscipit aliquam. Praesent in eros vestibulum mi adipiscing adipiscing. Morbi facilisis. Curabitur ornare consequat nunc. Aenean vel metus. Ut posuere viverra nulla. Aliquam erat volutpat. Pellentesque convallis. Maecenas feugiat, tellus pellentesque pretium posuere, felis lorem euismod felis, eu ornare leo nisi vel felis. Mauris consectetur tortor et purus.

      Mauris eleifend est et turpis. Duis id erat. Suspendisse potenti. Aliquam vulputate, pede vel vehicula accumsan, mi neque rutrum erat, eu congue orci lorem eget lorem. Vestibulum non ante. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Fusce sodales. Quisque eu urna vel enim commodo pellentesque. Praesent eu risus hendrerit ligula tempus pretium. Curabitur lorem enim, pretium nec, feugiat nec, luctus a, lacus.

      Duis cursus. Maecenas ligula eros, blandit nec, pharetra at, semper at, magna. Nullam ac lacus. Nulla facilisi. Praesent viverra justo vitae neque. Praesent blandit adipiscing velit. Suspendisse potenti. Donec mattis, pede vel pharetra blandit, magna ligula faucibus eros, id euismod lacus dolor eget odio. Nam scelerisque. Donec non libero sed nulla mattis commodo. Ut sagittis. Donec nisi lectus, feugiat porttitor, tempor ac, tempor vitae, pede. Aenean vehicula velit eu tellus interdum rutrum. Maecenas commodo. Pellentesque nec elit. Fusce in lacus. Vivamus a libero vitae lectus hendrerit hendrerit.

      Control tabs with a slider.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/sortable/connect-lists-through-tabs.html ================================================ jQuery UI Sortable - Connect lists with Tabs
      • Item 1
      • Item 2
      • Item 3
      • Item 4
      • Item 5
      • Item 1
      • Item 2
      • Item 3
      • Item 4
      • Item 5

      Sort items from one list into another and vice versa, by dropping the list item on the appropriate tab above.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/sortable/connect-lists.html ================================================ jQuery UI Sortable - Connect lists
      • Item 1
      • Item 2
      • Item 3
      • Item 4
      • Item 5
      • Item 1
      • Item 2
      • Item 3
      • Item 4
      • Item 5

      Sort items from one list into another and vice versa, by passing a selector into the connectWith option. The simplest way to do this is to group all related lists with a CSS class, and then pass that class into the sortable function (i.e., connectWith: '.myclass').

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/sortable/default.html ================================================ jQuery UI Sortable - Default functionality
      • Item 1
      • Item 2
      • Item 3
      • Item 4
      • Item 5
      • Item 6
      • Item 7

      Enable a group of DOM elements to be sortable. Click on and drag an element to a new spot within the list, and the other items will adjust to fit. By default, sortable items share draggable properties.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/sortable/delay-start.html ================================================ jQuery UI Sortable - Delay start

      Time delay of 300ms:

      • Item 1
      • Item 2
      • Item 3
      • Item 4

      Distance delay of 15px:

      • Item 1
      • Item 2
      • Item 3
      • Item 4

      Prevent accidental sorting either by delay (time) or distance. Set a number of milliseconds the element needs to be dragged before sorting starts with the delay option. Set a distance in pixels the element needs to be dragged before sorting starts with the distance option.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/sortable/display-grid.html ================================================ jQuery UI Sortable - Display as grid
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12

      To arrange sortable items as a grid, give them identical dimensions and float them using CSS.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/sortable/empty-lists.html ================================================ jQuery UI Sortable - Handle empty lists
      • Can be dropped..
      • ..on an empty list
      • Item 3
      • Item 4
      • Item 5
      • Cannot be dropped..
      • ..on an empty list
      • Item 3
      • Item 4
      • Item 5

      Prevent all items in a list from being dropped into a separate, empty list using the dropOnEmpty option set to false. By default, sortable items can be dropped on empty lists.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/sortable/index.html ================================================ jQuery UI Sortable Demos ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/sortable/items.html ================================================ jQuery UI Sortable - Include / exclude items

      Specify which items are sortable:

      • Item 1
      • (I'm not sortable or a drop target)
      • (I'm not sortable or a drop target)
      • Item 4

      Cancel sorting (but keep as drop targets):

      • Item 1
      • (I'm not sortable)
      • (I'm not sortable)
      • Item 4

      Specify which items are eligible to sort by passing a jQuery selector into the items option. Items excluded from this option are not sortable, nor are they valid targets for sortable items.

      To only prevent sorting on certain items, pass a jQuery selector into the cancel option. Cancelled items remain valid sort targets for others.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/sortable/placeholder.html ================================================ jQuery UI Sortable - Drop placeholder
      • Item 1
      • Item 2
      • Item 3
      • Item 4
      • Item 5
      • Item 6
      • Item 7

      When dragging a sortable item to a new location, other items will make room for the that item by shifting to allow white space between them. Pass a class into the placeholder option to style that space to be visible. Use the boolean forcePlaceholderSize option to set dimensions on the placeholder.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/sortable/portlets.html ================================================ jQuery UI Sortable - Portlets
      Feeds
      Lorem ipsum dolor sit amet, consectetuer adipiscing elit
      News
      Lorem ipsum dolor sit amet, consectetuer adipiscing elit
      Shopping
      Lorem ipsum dolor sit amet, consectetuer adipiscing elit
      Links
      Lorem ipsum dolor sit amet, consectetuer adipiscing elit
      Images
      Lorem ipsum dolor sit amet, consectetuer adipiscing elit

      Enable portlets (styled divs) as sortables and use the connectWith option to allow sorting between columns.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/spinner/currency.html ================================================ jQuery UI Spinner - Default functionality

      Example of a donation form, with currency selection and amout spinner.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/spinner/decimal.html ================================================ jQuery UI Spinner - Decimal

      Example of a decimal spinner. Step is set to 0.01.
      The code handling the culture change reads the current spinner value, then changes the culture, then sets the value again, resulting in an updated formatting, based on the new culture.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/spinner/default.html ================================================ jQuery UI Spinner - Default functionality

      Default spinner.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/spinner/index.html ================================================ jQuery UI Spinner Demos ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/spinner/latlong.html ================================================ jQuery UI Spinner - Map

      Google Maps integration, using spinners to change latidude and longitude.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/spinner/overflow.html ================================================ jQuery UI Spinner - Overflow

      Overflowing spinner restricted to a range of -10 to 10. For anything above 10, it'll overflow to -10, and the other way round.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/spinner/time.html ================================================ jQuery UI Spinner - Time

      A custom widget extending spinner. Use the Globalization plugin to parse and output a timestamp, with custom step and page options. Cursor up/down spins minutes, page up/down spins hours.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/switchClass/default.html ================================================ jQuery UI Effects - switchClass Demo
      Etiam libero neque, luctus a, eleifend nec, semper at, lorem. Sed pede.
      Run Effect

      Click the button above to preview the effect.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/switchClass/index.html ================================================ jQuery UI Effects Demos ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/tabs/ajax/content1.html ================================================

      This content was loaded via ajax.

      Proin elit arcu, rutrum commodo, vehicula tempus, commodo a, risus. Curabitur nec arcu. Donec sollicitudin mi sit amet mauris. Nam elementum quam ullamcorper ante. Etiam aliquet massa et lorem. Mauris dapibus lacus auctor risus. Aenean tempor ullamcorper leo. Vivamus sed magna quis ligula eleifend adipiscing. Duis orci. Aliquam sodales tortor vitae ipsum. Aliquam nulla. Duis aliquam molestie erat. Ut et mauris vel pede varius sollicitudin. Sed ut dolor nec orci tincidunt interdum. Phasellus ipsum. Nunc tristique tempus lectus.

      Mauris vitae ante. Curabitur augue. Nulla purus nibh, lobortis ut, feugiat at, aliquam id, purus. Sed venenatis, lorem venenatis volutpat commodo, purus quam lacinia justo, mattis interdum pede pede a odio. Fusce nibh. Morbi nisl mauris, dapibus in, tristique eget, accumsan et, pede. Donec mauris risus, pulvinar ut, faucibus eu, mollis in, nunc. In augue massa, commodo a, cursus vehicula, varius eu, dui. Suspendisse sodales suscipit lorem. Morbi malesuada, eros quis condimentum dignissim, lectus nibh tristique urna, non bibendum diam massa vel risus. Morbi suscipit. Proin egestas, eros at scelerisque scelerisque, dolor lacus fringilla lacus, ut ullamcorper mi magna at quam. Aliquam sed elit. Aliquam turpis purus, congue quis, iaculis id, ullamcorper sit amet, justo. Maecenas sed mauris. Proin magna justo, interdum in, tincidunt eu, viverra eu, turpis. Suspendisse mollis. In magna. Phasellus pellentesque, urna pellentesque convallis pellentesque, augue sem blandit pede, at rhoncus libero nisl a odio.

      Sed vitae nibh non magna semper tempor. Duis dolor. Nam congue laoreet arcu. Fusce lobortis enim quis ligula. Maecenas commodo odio id mi. Maecenas scelerisque tellus eu odio. Etiam dolor purus, lacinia a, imperdiet in, aliquam et, eros. In pellentesque. Nullam ac massa. Integer et turpis. Ut quam augue, congue non, imperdiet id, eleifend ac, nisi. Etiam ac arcu. Cras iaculis accumsan erat. Nullam vulputate sapien nec nisi pretium rhoncus. Aliquam a nibh. Vivamus est ante, fermentum a, tincidunt ut, imperdiet nec, velit. Aenean non tortor. Sed nec mauris eget tellus condimentum rutrum.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/tabs/ajax/content2.html ================================================

      This other content was loaded via ajax.

      Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean nec turpis justo, et facilisis ligula. In congue interdum odio, a scelerisque eros posuere ac. Aenean massa tellus, dictum sit amet laoreet ut, aliquam in orci. Duis eu aliquam ligula. Nullam vel placerat ligula. Fusce venenatis viverra dictum. Phasellus dui dolor, imperdiet in sodales at, mattis sed libero. Morbi ac ipsum ligula. Quisque suscipit dui vel diam pretium nec cursus lacus malesuada. Donec sollicitudin, eros eget dignissim mollis, risus leo feugiat tellus, vel posuere nisl ipsum eu erat. Quisque posuere lacinia imperdiet. Quisque nunc leo, elementum quis ultricies et, vehicula sit amet turpis. Nullam sed nunc nec nibh condimentum mattis. Quisque sed ligula sit amet nisi ultricies bibendum eget id nisi.

      Proin ut erat vel nunc tincidunt commodo. Curabitur feugiat, nisi et vehicula viverra, nisl orci eleifend arcu, sed blandit lectus nisl quis nisi. In hac habitasse platea dictumst. In hac habitasse platea dictumst. Aenean rutrum gravida velit ac imperdiet. Integer vitae arcu risus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Proin tincidunt orci at leo egestas porta. Vivamus ac augue et enim bibendum hendrerit ut id urna. Donec sollicitudin pulvinar turpis vitae scelerisque. Etiam tempor porttitor est sed blandit. Phasellus varius consequat leo eget tincidunt. Aliquam ac dui lectus. In et consectetur orci. Duis posuere nulla ac turpis faucibus vestibulum. Sed ut velit et dolor rhoncus dapibus. Sed sit amet pellentesque est.

      Nam in volutpat orci. Morbi sit amet orci in erat egestas dignissim. Etiam mi sapien, tempus sed iaculis a, adipiscing quis tellus. Suspendisse potenti. Nam malesuada tristique vestibulum. In tempor tellus dignissim neque consectetur eu vestibulum nisl pellentesque. Phasellus ultrices cursus velit, id aliquam nisl fringilla quis. Cras varius elit sed urna ultrices congue. Sed ornare odio sed velit pellentesque id varius nisl sodales. Sed auctor ligula egestas mi pharetra ut consectetur erat pharetra.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/tabs/ajax/content3-slow.php ================================================

      This content was loaded via ajax, though it took a second.

      Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean nec turpis justo, et facilisis ligula. In congue interdum odio, a scelerisque eros posuere ac. Aenean massa tellus, dictum sit amet laoreet ut, aliquam in orci. Duis eu aliquam ligula. Nullam vel placerat ligula. Fusce venenatis viverra dictum. Phasellus dui dolor, imperdiet in sodales at, mattis sed libero. Morbi ac ipsum ligula. Quisque suscipit dui vel diam pretium nec cursus lacus malesuada. Donec sollicitudin, eros eget dignissim mollis, risus leo feugiat tellus, vel posuere nisl ipsum eu erat. Quisque posuere lacinia imperdiet. Quisque nunc leo, elementum quis ultricies et, vehicula sit amet turpis. Nullam sed nunc nec nibh condimentum mattis. Quisque sed ligula sit amet nisi ultricies bibendum eget id nisi.

      Proin ut erat vel nunc tincidunt commodo. Curabitur feugiat, nisi et vehicula viverra, nisl orci eleifend arcu, sed blandit lectus nisl quis nisi. In hac habitasse platea dictumst. In hac habitasse platea dictumst. Aenean rutrum gravida velit ac imperdiet. Integer vitae arcu risus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Proin tincidunt orci at leo egestas porta. Vivamus ac augue et enim bibendum hendrerit ut id urna. Donec sollicitudin pulvinar turpis vitae scelerisque. Etiam tempor porttitor est sed blandit. Phasellus varius consequat leo eget tincidunt. Aliquam ac dui lectus. In et consectetur orci. Duis posuere nulla ac turpis faucibus vestibulum. Sed ut velit et dolor rhoncus dapibus. Sed sit amet pellentesque est.

      Nam in volutpat orci. Morbi sit amet orci in erat egestas dignissim. Etiam mi sapien, tempus sed iaculis a, adipiscing quis tellus. Suspendisse potenti. Nam malesuada tristique vestibulum. In tempor tellus dignissim neque consectetur eu vestibulum nisl pellentesque. Phasellus ultrices cursus velit, id aliquam nisl fringilla quis. Cras varius elit sed urna ultrices congue. Sed ornare odio sed velit pellentesque id varius nisl sodales. Sed auctor ligula egestas mi pharetra ut consectetur erat pharetra.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/tabs/ajax/content4-broken.php ================================================ ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/tabs/ajax.html ================================================ jQuery UI Tabs - Content via Ajax

      Proin elit arcu, rutrum commodo, vehicula tempus, commodo a, risus. Curabitur nec arcu. Donec sollicitudin mi sit amet mauris. Nam elementum quam ullamcorper ante. Etiam aliquet massa et lorem. Mauris dapibus lacus auctor risus. Aenean tempor ullamcorper leo. Vivamus sed magna quis ligula eleifend adipiscing. Duis orci. Aliquam sodales tortor vitae ipsum. Aliquam nulla. Duis aliquam molestie erat. Ut et mauris vel pede varius sollicitudin. Sed ut dolor nec orci tincidunt interdum. Phasellus ipsum. Nunc tristique tempus lectus.

      Fetch external content via Ajax for the tabs by setting an href value in the tab links. While the Ajax request is waiting for a response, the tab label changes to say "Loading...", then returns to the normal label once loaded.

      Tabs 3 and 4 demonstrate slow-loading and broken AJAX tabs, and how to handle serverside errors in those cases. Note: These two require a webserver to interpret PHP. They won't work from the filesystem.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/tabs/bottom.html ================================================ jQuery UI Tabs - Tabs at bottom

      Proin elit arcu, rutrum commodo, vehicula tempus, commodo a, risus. Curabitur nec arcu. Donec sollicitudin mi sit amet mauris. Nam elementum quam ullamcorper ante. Etiam aliquet massa et lorem. Mauris dapibus lacus auctor risus. Aenean tempor ullamcorper leo. Vivamus sed magna quis ligula eleifend adipiscing. Duis orci. Aliquam sodales tortor vitae ipsum. Aliquam nulla. Duis aliquam molestie erat. Ut et mauris vel pede varius sollicitudin. Sed ut dolor nec orci tincidunt interdum. Phasellus ipsum. Nunc tristique tempus lectus.

      Morbi tincidunt, dui sit amet facilisis feugiat, odio metus gravida ante, ut pharetra massa metus id nunc. Duis scelerisque molestie turpis. Sed fringilla, massa eget luctus malesuada, metus eros molestie lectus, ut tempus eros massa ut dolor. Aenean aliquet fringilla sem. Suspendisse sed ligula in ligula suscipit aliquam. Praesent in eros vestibulum mi adipiscing adipiscing. Morbi facilisis. Curabitur ornare consequat nunc. Aenean vel metus. Ut posuere viverra nulla. Aliquam erat volutpat. Pellentesque convallis. Maecenas feugiat, tellus pellentesque pretium posuere, felis lorem euismod felis, eu ornare leo nisi vel felis. Mauris consectetur tortor et purus.

      Mauris eleifend est et turpis. Duis id erat. Suspendisse potenti. Aliquam vulputate, pede vel vehicula accumsan, mi neque rutrum erat, eu congue orci lorem eget lorem. Vestibulum non ante. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Fusce sodales. Quisque eu urna vel enim commodo pellentesque. Praesent eu risus hendrerit ligula tempus pretium. Curabitur lorem enim, pretium nec, feugiat nec, luctus a, lacus.

      Duis cursus. Maecenas ligula eros, blandit nec, pharetra at, semper at, magna. Nullam ac lacus. Nulla facilisi. Praesent viverra justo vitae neque. Praesent blandit adipiscing velit. Suspendisse potenti. Donec mattis, pede vel pharetra blandit, magna ligula faucibus eros, id euismod lacus dolor eget odio. Nam scelerisque. Donec non libero sed nulla mattis commodo. Ut sagittis. Donec nisi lectus, feugiat porttitor, tempor ac, tempor vitae, pede. Aenean vehicula velit eu tellus interdum rutrum. Maecenas commodo. Pellentesque nec elit. Fusce in lacus. Vivamus a libero vitae lectus hendrerit hendrerit.

      With some additional CSS (for positioning) and JS (to put the right classes on elements) the tabs can be placed below their content.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/tabs/collapsible.html ================================================ jQuery UI Tabs - Collapse content

      Click this tab again to close the content pane.

      Proin elit arcu, rutrum commodo, vehicula tempus, commodo a, risus. Curabitur nec arcu. Donec sollicitudin mi sit amet mauris. Nam elementum quam ullamcorper ante. Etiam aliquet massa et lorem. Mauris dapibus lacus auctor risus. Aenean tempor ullamcorper leo. Vivamus sed magna quis ligula eleifend adipiscing. Duis orci. Aliquam sodales tortor vitae ipsum. Aliquam nulla. Duis aliquam molestie erat. Ut et mauris vel pede varius sollicitudin. Sed ut dolor nec orci tincidunt interdum. Phasellus ipsum. Nunc tristique tempus lectus.

      Click this tab again to close the content pane.

      Morbi tincidunt, dui sit amet facilisis feugiat, odio metus gravida ante, ut pharetra massa metus id nunc. Duis scelerisque molestie turpis. Sed fringilla, massa eget luctus malesuada, metus eros molestie lectus, ut tempus eros massa ut dolor. Aenean aliquet fringilla sem. Suspendisse sed ligula in ligula suscipit aliquam. Praesent in eros vestibulum mi adipiscing adipiscing. Morbi facilisis. Curabitur ornare consequat nunc. Aenean vel metus. Ut posuere viverra nulla. Aliquam erat volutpat. Pellentesque convallis. Maecenas feugiat, tellus pellentesque pretium posuere, felis lorem euismod felis, eu ornare leo nisi vel felis. Mauris consectetur tortor et purus.

      Click this tab again to close the content pane.

      Duis cursus. Maecenas ligula eros, blandit nec, pharetra at, semper at, magna. Nullam ac lacus. Nulla facilisi. Praesent viverra justo vitae neque. Praesent blandit adipiscing velit. Suspendisse potenti. Donec mattis, pede vel pharetra blandit, magna ligula faucibus eros, id euismod lacus dolor eget odio. Nam scelerisque. Donec non libero sed nulla mattis commodo. Ut sagittis. Donec nisi lectus, feugiat porttitor, tempor ac, tempor vitae, pede. Aenean vehicula velit eu tellus interdum rutrum. Maecenas commodo. Pellentesque nec elit. Fusce in lacus. Vivamus a libero vitae lectus hendrerit hendrerit.

      Click the selected tab to toggle its content closed/open. To enable this functionality, set the collapsible option to true.

      collapsible: true
      
      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/tabs/default.html ================================================ jQuery UI Tabs - Default functionality

      Proin elit arcu, rutrum commodo, vehicula tempus, commodo a, risus. Curabitur nec arcu. Donec sollicitudin mi sit amet mauris. Nam elementum quam ullamcorper ante. Etiam aliquet massa et lorem. Mauris dapibus lacus auctor risus. Aenean tempor ullamcorper leo. Vivamus sed magna quis ligula eleifend adipiscing. Duis orci. Aliquam sodales tortor vitae ipsum. Aliquam nulla. Duis aliquam molestie erat. Ut et mauris vel pede varius sollicitudin. Sed ut dolor nec orci tincidunt interdum. Phasellus ipsum. Nunc tristique tempus lectus.

      Morbi tincidunt, dui sit amet facilisis feugiat, odio metus gravida ante, ut pharetra massa metus id nunc. Duis scelerisque molestie turpis. Sed fringilla, massa eget luctus malesuada, metus eros molestie lectus, ut tempus eros massa ut dolor. Aenean aliquet fringilla sem. Suspendisse sed ligula in ligula suscipit aliquam. Praesent in eros vestibulum mi adipiscing adipiscing. Morbi facilisis. Curabitur ornare consequat nunc. Aenean vel metus. Ut posuere viverra nulla. Aliquam erat volutpat. Pellentesque convallis. Maecenas feugiat, tellus pellentesque pretium posuere, felis lorem euismod felis, eu ornare leo nisi vel felis. Mauris consectetur tortor et purus.

      Mauris eleifend est et turpis. Duis id erat. Suspendisse potenti. Aliquam vulputate, pede vel vehicula accumsan, mi neque rutrum erat, eu congue orci lorem eget lorem. Vestibulum non ante. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Fusce sodales. Quisque eu urna vel enim commodo pellentesque. Praesent eu risus hendrerit ligula tempus pretium. Curabitur lorem enim, pretium nec, feugiat nec, luctus a, lacus.

      Duis cursus. Maecenas ligula eros, blandit nec, pharetra at, semper at, magna. Nullam ac lacus. Nulla facilisi. Praesent viverra justo vitae neque. Praesent blandit adipiscing velit. Suspendisse potenti. Donec mattis, pede vel pharetra blandit, magna ligula faucibus eros, id euismod lacus dolor eget odio. Nam scelerisque. Donec non libero sed nulla mattis commodo. Ut sagittis. Donec nisi lectus, feugiat porttitor, tempor ac, tempor vitae, pede. Aenean vehicula velit eu tellus interdum rutrum. Maecenas commodo. Pellentesque nec elit. Fusce in lacus. Vivamus a libero vitae lectus hendrerit hendrerit.

      Click tabs to swap between content that is broken into logical sections.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/tabs/index.html ================================================ jQuery UI Tabs Demos ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/tabs/manipulation.html ================================================ jQuery UI Tabs - Simple manipulation

      Proin elit arcu, rutrum commodo, vehicula tempus, commodo a, risus. Curabitur nec arcu. Donec sollicitudin mi sit amet mauris. Nam elementum quam ullamcorper ante. Etiam aliquet massa et lorem. Mauris dapibus lacus auctor risus. Aenean tempor ullamcorper leo. Vivamus sed magna quis ligula eleifend adipiscing. Duis orci. Aliquam sodales tortor vitae ipsum. Aliquam nulla. Duis aliquam molestie erat. Ut et mauris vel pede varius sollicitudin. Sed ut dolor nec orci tincidunt interdum. Phasellus ipsum. Nunc tristique tempus lectus.

      Simple tabs adding and removing.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/tabs/mouseover.html ================================================ jQuery UI Tabs - Open on mouseover

      Proin elit arcu, rutrum commodo, vehicula tempus, commodo a, risus. Curabitur nec arcu. Donec sollicitudin mi sit amet mauris. Nam elementum quam ullamcorper ante. Etiam aliquet massa et lorem. Mauris dapibus lacus auctor risus. Aenean tempor ullamcorper leo. Vivamus sed magna quis ligula eleifend adipiscing. Duis orci. Aliquam sodales tortor vitae ipsum. Aliquam nulla. Duis aliquam molestie erat. Ut et mauris vel pede varius sollicitudin. Sed ut dolor nec orci tincidunt interdum. Phasellus ipsum. Nunc tristique tempus lectus.

      Morbi tincidunt, dui sit amet facilisis feugiat, odio metus gravida ante, ut pharetra massa metus id nunc. Duis scelerisque molestie turpis. Sed fringilla, massa eget luctus malesuada, metus eros molestie lectus, ut tempus eros massa ut dolor. Aenean aliquet fringilla sem. Suspendisse sed ligula in ligula suscipit aliquam. Praesent in eros vestibulum mi adipiscing adipiscing. Morbi facilisis. Curabitur ornare consequat nunc. Aenean vel metus. Ut posuere viverra nulla. Aliquam erat volutpat. Pellentesque convallis. Maecenas feugiat, tellus pellentesque pretium posuere, felis lorem euismod felis, eu ornare leo nisi vel felis. Mauris consectetur tortor et purus.

      Mauris eleifend est et turpis. Duis id erat. Suspendisse potenti. Aliquam vulputate, pede vel vehicula accumsan, mi neque rutrum erat, eu congue orci lorem eget lorem. Vestibulum non ante. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Fusce sodales. Quisque eu urna vel enim commodo pellentesque. Praesent eu risus hendrerit ligula tempus pretium. Curabitur lorem enim, pretium nec, feugiat nec, luctus a, lacus.

      Duis cursus. Maecenas ligula eros, blandit nec, pharetra at, semper at, magna. Nullam ac lacus. Nulla facilisi. Praesent viverra justo vitae neque. Praesent blandit adipiscing velit. Suspendisse potenti. Donec mattis, pede vel pharetra blandit, magna ligula faucibus eros, id euismod lacus dolor eget odio. Nam scelerisque. Donec non libero sed nulla mattis commodo. Ut sagittis. Donec nisi lectus, feugiat porttitor, tempor ac, tempor vitae, pede. Aenean vehicula velit eu tellus interdum rutrum. Maecenas commodo. Pellentesque nec elit. Fusce in lacus. Vivamus a libero vitae lectus hendrerit hendrerit.

      Toggle sections open/closed on mouseover with the event option. The default value for event is "click."

      event: 'mouseover'
      
      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/tabs/sortable.html ================================================ jQuery UI Tabs - Sortable

      Proin elit arcu, rutrum commodo, vehicula tempus, commodo a, risus. Curabitur nec arcu. Donec sollicitudin mi sit amet mauris. Nam elementum quam ullamcorper ante. Etiam aliquet massa et lorem. Mauris dapibus lacus auctor risus. Aenean tempor ullamcorper leo. Vivamus sed magna quis ligula eleifend adipiscing. Duis orci. Aliquam sodales tortor vitae ipsum. Aliquam nulla. Duis aliquam molestie erat. Ut et mauris vel pede varius sollicitudin. Sed ut dolor nec orci tincidunt interdum. Phasellus ipsum. Nunc tristique tempus lectus.

      Morbi tincidunt, dui sit amet facilisis feugiat, odio metus gravida ante, ut pharetra massa metus id nunc. Duis scelerisque molestie turpis. Sed fringilla, massa eget luctus malesuada, metus eros molestie lectus, ut tempus eros massa ut dolor. Aenean aliquet fringilla sem. Suspendisse sed ligula in ligula suscipit aliquam. Praesent in eros vestibulum mi adipiscing adipiscing. Morbi facilisis. Curabitur ornare consequat nunc. Aenean vel metus. Ut posuere viverra nulla. Aliquam erat volutpat. Pellentesque convallis. Maecenas feugiat, tellus pellentesque pretium posuere, felis lorem euismod felis, eu ornare leo nisi vel felis. Mauris consectetur tortor et purus.

      Mauris eleifend est et turpis. Duis id erat. Suspendisse potenti. Aliquam vulputate, pede vel vehicula accumsan, mi neque rutrum erat, eu congue orci lorem eget lorem. Vestibulum non ante. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Fusce sodales. Quisque eu urna vel enim commodo pellentesque. Praesent eu risus hendrerit ligula tempus pretium. Curabitur lorem enim, pretium nec, feugiat nec, luctus a, lacus.

      Duis cursus. Maecenas ligula eros, blandit nec, pharetra at, semper at, magna. Nullam ac lacus. Nulla facilisi. Praesent viverra justo vitae neque. Praesent blandit adipiscing velit. Suspendisse potenti. Donec mattis, pede vel pharetra blandit, magna ligula faucibus eros, id euismod lacus dolor eget odio. Nam scelerisque. Donec non libero sed nulla mattis commodo. Ut sagittis. Donec nisi lectus, feugiat porttitor, tempor ac, tempor vitae, pede. Aenean vehicula velit eu tellus interdum rutrum. Maecenas commodo. Pellentesque nec elit. Fusce in lacus. Vivamus a libero vitae lectus hendrerit hendrerit.

      Drag the tabs above to re-order them.

      Making tabs sortable is as simple as calling .sortable() on the .ui-tabs-nav element.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/tabs/vertical.html ================================================ jQuery UI Tabs - Vertical Tabs functionality

      Content heading 1

      Proin elit arcu, rutrum commodo, vehicula tempus, commodo a, risus. Curabitur nec arcu. Donec sollicitudin mi sit amet mauris. Nam elementum quam ullamcorper ante. Etiam aliquet massa et lorem. Mauris dapibus lacus auctor risus. Aenean tempor ullamcorper leo. Vivamus sed magna quis ligula eleifend adipiscing. Duis orci. Aliquam sodales tortor vitae ipsum. Aliquam nulla. Duis aliquam molestie erat. Ut et mauris vel pede varius sollicitudin. Sed ut dolor nec orci tincidunt interdum. Phasellus ipsum. Nunc tristique tempus lectus.

      Content heading 2

      Morbi tincidunt, dui sit amet facilisis feugiat, odio metus gravida ante, ut pharetra massa metus id nunc. Duis scelerisque molestie turpis. Sed fringilla, massa eget luctus malesuada, metus eros molestie lectus, ut tempus eros massa ut dolor. Aenean aliquet fringilla sem. Suspendisse sed ligula in ligula suscipit aliquam. Praesent in eros vestibulum mi adipiscing adipiscing. Morbi facilisis. Curabitur ornare consequat nunc. Aenean vel metus. Ut posuere viverra nulla. Aliquam erat volutpat. Pellentesque convallis. Maecenas feugiat, tellus pellentesque pretium posuere, felis lorem euismod felis, eu ornare leo nisi vel felis. Mauris consectetur tortor et purus.

      Content heading 3

      Mauris eleifend est et turpis. Duis id erat. Suspendisse potenti. Aliquam vulputate, pede vel vehicula accumsan, mi neque rutrum erat, eu congue orci lorem eget lorem. Vestibulum non ante. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Fusce sodales. Quisque eu urna vel enim commodo pellentesque. Praesent eu risus hendrerit ligula tempus pretium. Curabitur lorem enim, pretium nec, feugiat nec, luctus a, lacus.

      Duis cursus. Maecenas ligula eros, blandit nec, pharetra at, semper at, magna. Nullam ac lacus. Nulla facilisi. Praesent viverra justo vitae neque. Praesent blandit adipiscing velit. Suspendisse potenti. Donec mattis, pede vel pharetra blandit, magna ligula faucibus eros, id euismod lacus dolor eget odio. Nam scelerisque. Donec non libero sed nulla mattis commodo. Ut sagittis. Donec nisi lectus, feugiat porttitor, tempor ac, tempor vitae, pede. Aenean vehicula velit eu tellus interdum rutrum. Maecenas commodo. Pellentesque nec elit. Fusce in lacus. Vivamus a libero vitae lectus hendrerit hendrerit.

      Click tabs to swap between content that is broken into logical sections.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/toggle/default.html ================================================ jQuery UI Effects - Toggle Demo

      Toggle

      Etiam libero neque, luctus a, eleifend nec, semper at, lorem. Sed pede. Nulla lorem metus, adipiscing ut, luctus sed, hendrerit vitae, mi.

      Run Effect

      Click the button above to preview the effect.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/toggle/index.html ================================================ jQuery UI Effects Demos ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/toggleClass/default.html ================================================ jQuery UI Effects - toggleClass Demo
      Etiam libero neque, luctus a, eleifend nec, semper at, lorem. Sed pede.
      Run Effect

      Click the button above to preview the effect.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/toggleClass/index.html ================================================ jQuery UI Effects Demos ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/tooltip/ajax/content1.html ================================================

      This content was loaded via ajax.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/tooltip/ajax/content2.html ================================================

      This other content was loaded via ajax.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/tooltip/custom-animation.html ================================================ jQuery UI Tooltip - Custom animation demo

      There are various ways to customize the animation of a tooltip.

      You can use the show and hide options.

      You can also use the open event.

      This demo shows how to customize animations using the show and hide options, as well as the open event.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/tooltip/custom-content.html ================================================ jQuery UI Tooltip - Custom content

      St. Stephen's Cathedral

      Vienna, Austria

      St. Stephen's Cathedral

      Tower Bridge

      London, England

      Tower Bridge

      All images are part of Wikimedia Commons and are licensed under CC BY-SA 3.0 by the copyright holder.

      Shows how to combine different event delegated tooltips into a single instance, by customizing the items and content options.

      We realize you may want to interact with the map tooltips. This is a planned feature for a future version.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/tooltip/custom-style.html ================================================ jQuery UI Tooltip - Default functionality

      Tooltips can be attached to any element. When you hover the element with your mouse, the title attribute is displayed in a little box next to the element, just like a native tooltip.

      But as it's not a native tooltip, it can be styled. Any themes built with ThemeRoller will also style tooltips accordingly.

      Tooltips are also useful for form elements, to show some additional information in the context of each field.

      Hover the field to see the tooltip.

      Hover the links above or use the tab key to cycle the focus on each element.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/tooltip/default.html ================================================ jQuery UI Tooltip - Default functionality

      Tooltips can be attached to any element. When you hover the element with your mouse, the title attribute is displayed in a little box next to the element, just like a native tooltip.

      But as it's not a native tooltip, it can be styled. Any themes built with ThemeRoller will also style tooltips accordingly.

      Tooltips are also useful for form elements, to show some additional information in the context of each field.

      Hover the field to see the tooltip.

      Hover the links above or use the tab key to cycle the focus on each element.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/tooltip/forms.html ================================================ jQuery UI Tooltip - Default demo

      Use the button below to display the help texts, or just focus or mouseover the indivdual inputs.

      A fixed width is defined in CSS to make the tooltips look consistent when displayed all at once.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/tooltip/index.html ================================================ jQuery UI Tooltip Demos ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/tooltip/tracking.html ================================================ jQuery UI Tooltip - Track the mouse

      Tooltips can be attached to any element. When you hover the element with your mouse, the title attribute is displayed in a little box next to the element, just like a native tooltip.

      But as it's not a native tooltip, it can be styled. Any themes built with ThemeRoller will also style tooltips accordingly.

      Tooltips are also useful for form elements, to show some additional information in the context of each field.

      Hover the field to see the tooltip.

      Here the tooltips are positioned relative to the mouse, and follow the mouse while it moves above the element.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/tooltip/video-player.html ================================================ jQuery UI Tooltip - Video Player demo
      Here Be Video (HTML5?)

      A fake video player with like/share/stats button, each with a custom-styled tooltip.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/widget/default.html ================================================ jQuery UI Widget - Default functionality
      color me
      color me
      color me

      This demo shows a simple custom widget built using the widget factory (jquery.ui.widget.js).

      The three boxes are initialized in different ways. Clicking them changes their background color. View source to see how it works, its heavily commented

      For more details on the widget factory, visit the jQuery UI planning wiki.

      ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/demos/widget/index.html ================================================ jQuery UI Widget Demo ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/external/globalize.culture.de-DE.js ================================================ /* * Globalize Culture de-DE * * http://github.com/jquery/globalize * * Copyright Software Freedom Conservancy, Inc. * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * * This file was generated by the Globalize Culture Generator * Translation: bugs found in this file need to be fixed in the generator */ (function( window, undefined ) { var Globalize; if ( typeof require !== "undefined" && typeof exports !== "undefined" && typeof module !== "undefined" ) { // Assume CommonJS Globalize = require( "globalize" ); } else { // Global variable Globalize = window.Globalize; } Globalize.addCultureInfo( "de-DE", "default", { name: "de-DE", englishName: "German (Germany)", nativeName: "Deutsch (Deutschland)", language: "de", numberFormat: { ",": ".", ".": ",", NaN: "n. def.", negativeInfinity: "-unendlich", positiveInfinity: "+unendlich", percent: { pattern: ["-n%","n%"], ",": ".", ".": "," }, currency: { pattern: ["-n $","n $"], ",": ".", ".": ",", symbol: "€" } }, calendars: { standard: { "/": ".", firstDay: 1, days: { names: ["Sonntag","Montag","Dienstag","Mittwoch","Donnerstag","Freitag","Samstag"], namesAbbr: ["So","Mo","Di","Mi","Do","Fr","Sa"], namesShort: ["So","Mo","Di","Mi","Do","Fr","Sa"] }, months: { names: ["Januar","Februar","März","April","Mai","Juni","Juli","August","September","Oktober","November","Dezember",""], namesAbbr: ["Jan","Feb","Mrz","Apr","Mai","Jun","Jul","Aug","Sep","Okt","Nov","Dez",""] }, AM: null, PM: null, eras: [{"name":"n. Chr.","start":null,"offset":0}], patterns: { d: "dd.MM.yyyy", D: "dddd, d. MMMM yyyy", t: "HH:mm", T: "HH:mm:ss", f: "dddd, d. MMMM yyyy HH:mm", F: "dddd, d. MMMM yyyy HH:mm:ss", M: "dd MMMM", Y: "MMMM yyyy" } } } }); }( this )); ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/external/globalize.culture.ja-JP.js ================================================ /* * Globalize Culture ja-JP * * http://github.com/jquery/globalize * * Copyright Software Freedom Conservancy, Inc. * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * * This file was generated by the Globalize Culture Generator * Translation: bugs found in this file need to be fixed in the generator */ (function( window, undefined ) { var Globalize; if ( typeof require !== "undefined" && typeof exports !== "undefined" && typeof module !== "undefined" ) { // Assume CommonJS Globalize = require( "globalize" ); } else { // Global variable Globalize = window.Globalize; } Globalize.addCultureInfo( "ja-JP", "default", { name: "ja-JP", englishName: "Japanese (Japan)", nativeName: "日本語 (日本)", language: "ja", numberFormat: { NaN: "NaN (非数値)", negativeInfinity: "-∞", positiveInfinity: "+∞", percent: { pattern: ["-n%","n%"] }, currency: { pattern: ["-$n","$n"], decimals: 0, symbol: "¥" } }, calendars: { standard: { days: { names: ["日曜日","月曜日","火曜日","水曜日","木曜日","金曜日","土曜日"], namesAbbr: ["日","月","火","水","木","金","土"], namesShort: ["日","月","火","水","木","金","土"] }, months: { names: ["1月","2月","3月","4月","5月","6月","7月","8月","9月","10月","11月","12月",""], namesAbbr: ["1","2","3","4","5","6","7","8","9","10","11","12",""] }, AM: ["午前","午前","午前"], PM: ["午後","午後","午後"], eras: [{"name":"西暦","start":null,"offset":0}], patterns: { d: "yyyy/MM/dd", D: "yyyy'年'M'月'd'日'", t: "H:mm", T: "H:mm:ss", f: "yyyy'年'M'月'd'日' H:mm", F: "yyyy'年'M'月'd'日' H:mm:ss", M: "M'月'd'日'", Y: "yyyy'年'M'月'" } }, Japanese: { name: "Japanese", days: { names: ["日曜日","月曜日","火曜日","水曜日","木曜日","金曜日","土曜日"], namesAbbr: ["日","月","火","水","木","金","土"], namesShort: ["日","月","火","水","木","金","土"] }, months: { names: ["1月","2月","3月","4月","5月","6月","7月","8月","9月","10月","11月","12月",""], namesAbbr: ["1","2","3","4","5","6","7","8","9","10","11","12",""] }, AM: ["午前","午前","午前"], PM: ["午後","午後","午後"], eras: [{"name":"平成","start":null,"offset":1867},{"name":"昭和","start":-1812153600000,"offset":1911},{"name":"大正","start":-1357603200000,"offset":1925},{"name":"明治","start":60022080000,"offset":1988}], twoDigitYearMax: 99, patterns: { d: "gg y/M/d", D: "gg y'年'M'月'd'日'", t: "H:mm", T: "H:mm:ss", f: "gg y'年'M'月'd'日' H:mm", F: "gg y'年'M'月'd'日' H:mm:ss", M: "M'月'd'日'", Y: "gg y'年'M'月'" } } } }); }( this )); ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/external/globalize.js ================================================ /*! * Globalize * * http://github.com/jquery/globalize * * Copyright Software Freedom Conservancy, Inc. * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license */ (function( window, undefined ) { var Globalize, // private variables regexHex, regexInfinity, regexParseFloat, regexTrim, // private JavaScript utility functions arrayIndexOf, endsWith, extend, isArray, isFunction, isObject, startsWith, trim, truncate, zeroPad, // private Globalization utility functions appendPreOrPostMatch, expandFormat, formatDate, formatNumber, getTokenRegExp, getEra, getEraYear, parseExact, parseNegativePattern; // Global variable (Globalize) or CommonJS module (globalize) Globalize = function( cultureSelector ) { return new Globalize.prototype.init( cultureSelector ); }; if ( typeof require !== "undefined" && typeof exports !== "undefined" && typeof module !== "undefined" ) { // Assume CommonJS module.exports = Globalize; } else { // Export as global variable window.Globalize = Globalize; } Globalize.cultures = {}; Globalize.prototype = { constructor: Globalize, init: function( cultureSelector ) { this.cultures = Globalize.cultures; this.cultureSelector = cultureSelector; return this; } }; Globalize.prototype.init.prototype = Globalize.prototype; // 1. When defining a culture, all fields are required except the ones stated as optional. // 2. Each culture should have a ".calendars" object with at least one calendar named "standard" // which serves as the default calendar in use by that culture. // 3. Each culture should have a ".calendar" object which is the current calendar being used, // it may be dynamically changed at any time to one of the calendars in ".calendars". Globalize.cultures[ "default" ] = { // A unique name for the culture in the form - name: "en", // the name of the culture in the english language englishName: "English", // the name of the culture in its own language nativeName: "English", // whether the culture uses right-to-left text isRTL: false, // "language" is used for so-called "specific" cultures. // For example, the culture "es-CL" means "Spanish, in Chili". // It represents the Spanish-speaking culture as it is in Chili, // which might have different formatting rules or even translations // than Spanish in Spain. A "neutral" culture is one that is not // specific to a region. For example, the culture "es" is the generic // Spanish culture, which may be a more generalized version of the language // that may or may not be what a specific culture expects. // For a specific culture like "es-CL", the "language" field refers to the // neutral, generic culture information for the language it is using. // This is not always a simple matter of the string before the dash. // For example, the "zh-Hans" culture is netural (Simplified Chinese). // And the "zh-SG" culture is Simplified Chinese in Singapore, whose lanugage // field is "zh-CHS", not "zh". // This field should be used to navigate from a specific culture to it's // more general, neutral culture. If a culture is already as general as it // can get, the language may refer to itself. language: "en", // numberFormat defines general number formatting rules, like the digits in // each grouping, the group separator, and how negative numbers are displayed. numberFormat: { // [negativePattern] // Note, numberFormat.pattern has no "positivePattern" unlike percent and currency, // but is still defined as an array for consistency with them. // negativePattern: one of "(n)|-n|- n|n-|n -" pattern: [ "-n" ], // number of decimal places normally shown decimals: 2, // string that separates number groups, as in 1,000,000 ",": ",", // string that separates a number from the fractional portion, as in 1.99 ".": ".", // array of numbers indicating the size of each number group. // TODO: more detailed description and example groupSizes: [ 3 ], // symbol used for positive numbers "+": "+", // symbol used for negative numbers "-": "-", // symbol used for NaN (Not-A-Number) NaN: "NaN", // symbol used for Negative Infinity negativeInfinity: "-Infinity", // symbol used for Positive Infinity positiveInfinity: "Infinity", percent: { // [negativePattern, positivePattern] // negativePattern: one of "-n %|-n%|-%n|%-n|%n-|n-%|n%-|-% n|n %-|% n-|% -n|n- %" // positivePattern: one of "n %|n%|%n|% n" pattern: [ "-n %", "n %" ], // number of decimal places normally shown decimals: 2, // array of numbers indicating the size of each number group. // TODO: more detailed description and example groupSizes: [ 3 ], // string that separates number groups, as in 1,000,000 ",": ",", // string that separates a number from the fractional portion, as in 1.99 ".": ".", // symbol used to represent a percentage symbol: "%" }, currency: { // [negativePattern, positivePattern] // negativePattern: one of "($n)|-$n|$-n|$n-|(n$)|-n$|n-$|n$-|-n $|-$ n|n $-|$ n-|$ -n|n- $|($ n)|(n $)" // positivePattern: one of "$n|n$|$ n|n $" pattern: [ "($n)", "$n" ], // number of decimal places normally shown decimals: 2, // array of numbers indicating the size of each number group. // TODO: more detailed description and example groupSizes: [ 3 ], // string that separates number groups, as in 1,000,000 ",": ",", // string that separates a number from the fractional portion, as in 1.99 ".": ".", // symbol used to represent currency symbol: "$" } }, // calendars defines all the possible calendars used by this culture. // There should be at least one defined with name "standard", and is the default // calendar used by the culture. // A calendar contains information about how dates are formatted, information about // the calendar's eras, a standard set of the date formats, // translations for day and month names, and if the calendar is not based on the Gregorian // calendar, conversion functions to and from the Gregorian calendar. calendars: { standard: { // name that identifies the type of calendar this is name: "Gregorian_USEnglish", // separator of parts of a date (e.g. "/" in 11/05/1955) "/": "/", // separator of parts of a time (e.g. ":" in 05:44 PM) ":": ":", // the first day of the week (0 = Sunday, 1 = Monday, etc) firstDay: 0, days: { // full day names names: [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ], // abbreviated day names namesAbbr: [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ], // shortest day names namesShort: [ "Su", "Mo", "Tu", "We", "Th", "Fr", "Sa" ] }, months: { // full month names (13 months for lunar calendards -- 13th month should be "" if not lunar) names: [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" ], // abbreviated month names namesAbbr: [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "" ] }, // AM and PM designators in one of these forms: // The usual view, and the upper and lower case versions // [ standard, lowercase, uppercase ] // The culture does not use AM or PM (likely all standard date formats use 24 hour time) // null AM: [ "AM", "am", "AM" ], PM: [ "PM", "pm", "PM" ], eras: [ // eras in reverse chronological order. // name: the name of the era in this culture (e.g. A.D., C.E.) // start: when the era starts in ticks (gregorian, gmt), null if it is the earliest supported era. // offset: offset in years from gregorian calendar { "name": "A.D.", "start": null, "offset": 0 } ], // when a two digit year is given, it will never be parsed as a four digit // year greater than this year (in the appropriate era for the culture) // Set it as a full year (e.g. 2029) or use an offset format starting from // the current year: "+19" would correspond to 2029 if the current year 2010. twoDigitYearMax: 2029, // set of predefined date and time patterns used by the culture // these represent the format someone in this culture would expect // to see given the portions of the date that are shown. patterns: { // short date pattern d: "M/d/yyyy", // long date pattern D: "dddd, MMMM dd, yyyy", // short time pattern t: "h:mm tt", // long time pattern T: "h:mm:ss tt", // long date, short time pattern f: "dddd, MMMM dd, yyyy h:mm tt", // long date, long time pattern F: "dddd, MMMM dd, yyyy h:mm:ss tt", // month/day pattern M: "MMMM dd", // month/year pattern Y: "yyyy MMMM", // S is a sortable format that does not vary by culture S: "yyyy\u0027-\u0027MM\u0027-\u0027dd\u0027T\u0027HH\u0027:\u0027mm\u0027:\u0027ss" } // optional fields for each calendar: /* monthsGenitive: Same as months but used when the day preceeds the month. Omit if the culture has no genitive distinction in month names. For an explaination of genitive months, see http://blogs.msdn.com/michkap/archive/2004/12/25/332259.aspx convert: Allows for the support of non-gregorian based calendars. This convert object is used to to convert a date to and from a gregorian calendar date to handle parsing and formatting. The two functions: fromGregorian( date ) Given the date as a parameter, return an array with parts [ year, month, day ] corresponding to the non-gregorian based year, month, and day for the calendar. toGregorian( year, month, day ) Given the non-gregorian year, month, and day, return a new Date() object set to the corresponding date in the gregorian calendar. */ } }, // For localized strings messages: {} }; Globalize.cultures[ "default" ].calendar = Globalize.cultures[ "default" ].calendars.standard; Globalize.cultures[ "en" ] = Globalize.cultures[ "default" ]; Globalize.cultureSelector = "en"; // // private variables // regexHex = /^0x[a-f0-9]+$/i; regexInfinity = /^[+-]?infinity$/i; regexParseFloat = /^[+-]?\d*\.?\d*(e[+-]?\d+)?$/; regexTrim = /^\s+|\s+$/g; // // private JavaScript utility functions // arrayIndexOf = function( array, item ) { if ( array.indexOf ) { return array.indexOf( item ); } for ( var i = 0, length = array.length; i < length; i++ ) { if ( array[i] === item ) { return i; } } return -1; }; endsWith = function( value, pattern ) { return value.substr( value.length - pattern.length ) === pattern; }; extend = function( deep ) { var options, name, src, copy, copyIsArray, clone, target = arguments[0] || {}, i = 1, length = arguments.length, deep = false; // Handle a deep copy situation if ( typeof target === "boolean" ) { deep = target; target = arguments[1] || {}; // skip the boolean and the target i = 2; } // Handle case when target is a string or something (possible in deep copy) if ( typeof target !== "object" && !isFunction(target) ) { target = {}; } for ( ; i < length; i++ ) { // Only deal with non-null/undefined values if ( (options = arguments[ i ]) != null ) { // Extend the base object for ( name in options ) { src = target[ name ]; copy = options[ name ]; // Prevent never-ending loop if ( target === copy ) { continue; } // Recurse if we're merging plain objects or arrays if ( deep && copy && ( isObject(copy) || (copyIsArray = isArray(copy)) ) ) { if ( copyIsArray ) { copyIsArray = false; clone = src && isArray(src) ? src : []; } else { clone = src && isObject(src) ? src : {}; } // Never move original objects, clone them target[ name ] = extend( deep, clone, copy ); // Don't bring in undefined values } else if ( copy !== undefined ) { target[ name ] = copy; } } } } // Return the modified object return target; }; isArray = Array.isArray || function( obj ) { return Object.prototype.toString.call( obj ) === "[object Array]"; }; isFunction = function( obj ) { return Object.prototype.toString.call( obj ) === "[object Function]" } isObject = function( obj ) { return Object.prototype.toString.call( obj ) === "[object Object]"; }; startsWith = function( value, pattern ) { return value.indexOf( pattern ) === 0; }; trim = function( value ) { return ( value + "" ).replace( regexTrim, "" ); }; truncate = function( value ) { return value | 0; }; zeroPad = function( str, count, left ) { var l; for ( l = str.length; l < count; l += 1 ) { str = ( left ? ("0" + str) : (str + "0") ); } return str; }; // // private Globalization utility functions // appendPreOrPostMatch = function( preMatch, strings ) { // appends pre- and post- token match strings while removing escaped characters. // Returns a single quote count which is used to determine if the token occurs // in a string literal. var quoteCount = 0, escaped = false; for ( var i = 0, il = preMatch.length; i < il; i++ ) { var c = preMatch.charAt( i ); switch ( c ) { case "\'": if ( escaped ) { strings.push( "\'" ); } else { quoteCount++; } escaped = false; break; case "\\": if ( escaped ) { strings.push( "\\" ); } escaped = !escaped; break; default: strings.push( c ); escaped = false; break; } } return quoteCount; }; expandFormat = function( cal, format ) { // expands unspecified or single character date formats into the full pattern. format = format || "F"; var pattern, patterns = cal.patterns, len = format.length; if ( len === 1 ) { pattern = patterns[ format ]; if ( !pattern ) { throw "Invalid date format string \'" + format + "\'."; } format = pattern; } else if ( len === 2 && format.charAt(0) === "%" ) { // %X escape format -- intended as a custom format string that is only one character, not a built-in format. format = format.charAt( 1 ); } return format; }; formatDate = function( value, format, culture ) { var cal = culture.calendar, convert = cal.convert; if ( !format || !format.length || format === "i" ) { var ret; if ( culture && culture.name.length ) { if ( convert ) { // non-gregorian calendar, so we cannot use built-in toLocaleString() ret = formatDate( value, cal.patterns.F, culture ); } else { var eraDate = new Date( value.getTime() ), era = getEra( value, cal.eras ); eraDate.setFullYear( getEraYear(value, cal, era) ); ret = eraDate.toLocaleString(); } } else { ret = value.toString(); } return ret; } var eras = cal.eras, sortable = format === "s"; format = expandFormat( cal, format ); // Start with an empty string ret = []; var hour, zeros = [ "0", "00", "000" ], foundDay, checkedDay, dayPartRegExp = /([^d]|^)(d|dd)([^d]|$)/g, quoteCount = 0, tokenRegExp = getTokenRegExp(), converted; function padZeros( num, c ) { var r, s = num + ""; if ( c > 1 && s.length < c ) { r = ( zeros[c - 2] + s); return r.substr( r.length - c, c ); } else { r = s; } return r; } function hasDay() { if ( foundDay || checkedDay ) { return foundDay; } foundDay = dayPartRegExp.test( format ); checkedDay = true; return foundDay; } function getPart( date, part ) { if ( converted ) { return converted[ part ]; } switch ( part ) { case 0: return date.getFullYear(); case 1: return date.getMonth(); case 2: return date.getDate(); } } if ( !sortable && convert ) { converted = convert.fromGregorian( value ); } for ( ; ; ) { // Save the current index var index = tokenRegExp.lastIndex, // Look for the next pattern ar = tokenRegExp.exec( format ); // Append the text before the pattern (or the end of the string if not found) var preMatch = format.slice( index, ar ? ar.index : format.length ); quoteCount += appendPreOrPostMatch( preMatch, ret ); if ( !ar ) { break; } // do not replace any matches that occur inside a string literal. if ( quoteCount % 2 ) { ret.push( ar[0] ); continue; } var current = ar[ 0 ], clength = current.length; switch ( current ) { case "ddd": //Day of the week, as a three-letter abbreviation case "dddd": // Day of the week, using the full name var names = ( clength === 3 ) ? cal.days.namesAbbr : cal.days.names; ret.push( names[value.getDay()] ); break; case "d": // Day of month, without leading zero for single-digit days case "dd": // Day of month, with leading zero for single-digit days foundDay = true; ret.push( padZeros( getPart(value, 2), clength ) ); break; case "MMM": // Month, as a three-letter abbreviation case "MMMM": // Month, using the full name var part = getPart( value, 1 ); ret.push( ( cal.monthsGenitive && hasDay() ) ? cal.monthsGenitive[ clength === 3 ? "namesAbbr" : "names" ][ part ] : cal.months[ clength === 3 ? "namesAbbr" : "names" ][ part ] ); break; case "M": // Month, as digits, with no leading zero for single-digit months case "MM": // Month, as digits, with leading zero for single-digit months ret.push( padZeros( getPart(value, 1) + 1, clength ) ); break; case "y": // Year, as two digits, but with no leading zero for years less than 10 case "yy": // Year, as two digits, with leading zero for years less than 10 case "yyyy": // Year represented by four full digits part = converted ? converted[ 0 ] : getEraYear( value, cal, getEra(value, eras), sortable ); if ( clength < 4 ) { part = part % 100; } ret.push( padZeros( part, clength ) ); break; case "h": // Hours with no leading zero for single-digit hours, using 12-hour clock case "hh": // Hours with leading zero for single-digit hours, using 12-hour clock hour = value.getHours() % 12; if ( hour === 0 ) hour = 12; ret.push( padZeros( hour, clength ) ); break; case "H": // Hours with no leading zero for single-digit hours, using 24-hour clock case "HH": // Hours with leading zero for single-digit hours, using 24-hour clock ret.push( padZeros( value.getHours(), clength ) ); break; case "m": // Minutes with no leading zero for single-digit minutes case "mm": // Minutes with leading zero for single-digit minutes ret.push( padZeros( value.getMinutes(), clength ) ); break; case "s": // Seconds with no leading zero for single-digit seconds case "ss": // Seconds with leading zero for single-digit seconds ret.push( padZeros( value.getSeconds(), clength ) ); break; case "t": // One character am/pm indicator ("a" or "p") case "tt": // Multicharacter am/pm indicator part = value.getHours() < 12 ? ( cal.AM ? cal.AM[0] : " " ) : ( cal.PM ? cal.PM[0] : " " ); ret.push( clength === 1 ? part.charAt(0) : part ); break; case "f": // Deciseconds case "ff": // Centiseconds case "fff": // Milliseconds ret.push( padZeros( value.getMilliseconds(), 3 ).substr( 0, clength ) ); break; case "z": // Time zone offset, no leading zero case "zz": // Time zone offset with leading zero hour = value.getTimezoneOffset() / 60; ret.push( ( hour <= 0 ? "+" : "-" ) + padZeros( Math.floor(Math.abs(hour)), clength ) ); break; case "zzz": // Time zone offset with leading zero hour = value.getTimezoneOffset() / 60; ret.push( ( hour <= 0 ? "+" : "-" ) + padZeros( Math.floor(Math.abs(hour)), 2 ) // Hard coded ":" separator, rather than using cal.TimeSeparator // Repeated here for consistency, plus ":" was already assumed in date parsing. + ":" + padZeros( Math.abs(value.getTimezoneOffset() % 60), 2 ) ); break; case "g": case "gg": if ( cal.eras ) { ret.push( cal.eras[ getEra(value, eras) ].name ); } break; case "/": ret.push( cal["/"] ); break; default: throw "Invalid date format pattern \'" + current + "\'."; break; } } return ret.join( "" ); }; // formatNumber (function() { var expandNumber; expandNumber = function( number, precision, formatInfo ) { var groupSizes = formatInfo.groupSizes, curSize = groupSizes[ 0 ], curGroupIndex = 1, factor = Math.pow( 10, precision ), rounded = Math.round( number * factor ) / factor; if ( !isFinite(rounded) ) { rounded = number; } number = rounded; var numberString = number+"", right = "", split = numberString.split( /e/i ), exponent = split.length > 1 ? parseInt( split[1], 10 ) : 0; numberString = split[ 0 ]; split = numberString.split( "." ); numberString = split[ 0 ]; right = split.length > 1 ? split[ 1 ] : ""; var l; if ( exponent > 0 ) { right = zeroPad( right, exponent, false ); numberString += right.slice( 0, exponent ); right = right.substr( exponent ); } else if ( exponent < 0 ) { exponent = -exponent; numberString = zeroPad( numberString, exponent + 1 ); right = numberString.slice( -exponent, numberString.length ) + right; numberString = numberString.slice( 0, -exponent ); } if ( precision > 0 ) { right = formatInfo[ "." ] + ( (right.length > precision) ? right.slice(0, precision) : zeroPad(right, precision) ); } else { right = ""; } var stringIndex = numberString.length - 1, sep = formatInfo[ "," ], ret = ""; while ( stringIndex >= 0 ) { if ( curSize === 0 || curSize > stringIndex ) { return numberString.slice( 0, stringIndex + 1 ) + ( ret.length ? (sep + ret + right) : right ); } ret = numberString.slice( stringIndex - curSize + 1, stringIndex + 1 ) + ( ret.length ? (sep + ret) : "" ); stringIndex -= curSize; if ( curGroupIndex < groupSizes.length ) { curSize = groupSizes[ curGroupIndex ]; curGroupIndex++; } } return numberString.slice( 0, stringIndex + 1 ) + sep + ret + right; }; formatNumber = function( value, format, culture ) { if ( !isFinite(value) ) { if ( value === Infinity ) { return culture.numberFormat.positiveInfinity; } if ( value === -Infinity ) { return culture.numberFormat.negativeInfinity; } return culture.numberFormat.NaN; } if ( !format || format === "i" ) { return culture.name.length ? value.toLocaleString() : value.toString(); } format = format || "D"; var nf = culture.numberFormat, number = Math.abs( value ), precision = -1, pattern; if ( format.length > 1 ) precision = parseInt( format.slice(1), 10 ); var current = format.charAt( 0 ).toUpperCase(), formatInfo; switch ( current ) { case "D": pattern = "n"; number = truncate( number ); if ( precision !== -1 ) { number = zeroPad( "" + number, precision, true ); } if ( value < 0 ) number = "-" + number; break; case "N": formatInfo = nf; // fall through case "C": formatInfo = formatInfo || nf.currency; // fall through case "P": formatInfo = formatInfo || nf.percent; pattern = value < 0 ? formatInfo.pattern[ 0 ] : ( formatInfo.pattern[1] || "n" ); if ( precision === -1 ) precision = formatInfo.decimals; number = expandNumber( number * (current === "P" ? 100 : 1), precision, formatInfo ); break; default: throw "Bad number format specifier: " + current; } var patternParts = /n|\$|-|%/g, ret = ""; for ( ; ; ) { var index = patternParts.lastIndex, ar = patternParts.exec( pattern ); ret += pattern.slice( index, ar ? ar.index : pattern.length ); if ( !ar ) { break; } switch ( ar[0] ) { case "n": ret += number; break; case "$": ret += nf.currency.symbol; break; case "-": // don't make 0 negative if ( /[1-9]/.test(number) ) { ret += nf[ "-" ]; } break; case "%": ret += nf.percent.symbol; break; } } return ret; }; }()); getTokenRegExp = function() { // regular expression for matching date and time tokens in format strings. return /\/|dddd|ddd|dd|d|MMMM|MMM|MM|M|yyyy|yy|y|hh|h|HH|H|mm|m|ss|s|tt|t|fff|ff|f|zzz|zz|z|gg|g/g; }; getEra = function( date, eras ) { if ( !eras ) return 0; var start, ticks = date.getTime(); for ( var i = 0, l = eras.length; i < l; i++ ) { start = eras[ i ].start; if ( start === null || ticks >= start ) { return i; } } return 0; }; getEraYear = function( date, cal, era, sortable ) { var year = date.getFullYear(); if ( !sortable && cal.eras ) { // convert normal gregorian year to era-shifted gregorian // year by subtracting the era offset year -= cal.eras[ era ].offset; } return year; }; // parseExact (function() { var expandYear, getDayIndex, getMonthIndex, getParseRegExp, outOfRange, toUpper, toUpperArray; expandYear = function( cal, year ) { // expands 2-digit year into 4 digits. var now = new Date(), era = getEra( now ); if ( year < 100 ) { var twoDigitYearMax = cal.twoDigitYearMax; twoDigitYearMax = typeof twoDigitYearMax === "string" ? new Date().getFullYear() % 100 + parseInt( twoDigitYearMax, 10 ) : twoDigitYearMax; var curr = getEraYear( now, cal, era ); year += curr - ( curr % 100 ); if ( year > twoDigitYearMax ) { year -= 100; } } return year; }; getDayIndex = function ( cal, value, abbr ) { var ret, days = cal.days, upperDays = cal._upperDays; if ( !upperDays ) { cal._upperDays = upperDays = [ toUpperArray( days.names ), toUpperArray( days.namesAbbr ), toUpperArray( days.namesShort ) ]; } value = toUpper( value ); if ( abbr ) { ret = arrayIndexOf( upperDays[1], value ); if ( ret === -1 ) { ret = arrayIndexOf( upperDays[2], value ); } } else { ret = arrayIndexOf( upperDays[0], value ); } return ret; }; getMonthIndex = function( cal, value, abbr ) { var months = cal.months, monthsGen = cal.monthsGenitive || cal.months, upperMonths = cal._upperMonths, upperMonthsGen = cal._upperMonthsGen; if ( !upperMonths ) { cal._upperMonths = upperMonths = [ toUpperArray( months.names ), toUpperArray( months.namesAbbr ) ]; cal._upperMonthsGen = upperMonthsGen = [ toUpperArray( monthsGen.names ), toUpperArray( monthsGen.namesAbbr ) ]; } value = toUpper( value ); var i = arrayIndexOf( abbr ? upperMonths[1] : upperMonths[0], value ); if ( i < 0 ) { i = arrayIndexOf( abbr ? upperMonthsGen[1] : upperMonthsGen[0], value ); } return i; }; getParseRegExp = function( cal, format ) { // converts a format string into a regular expression with groups that // can be used to extract date fields from a date string. // check for a cached parse regex. var re = cal._parseRegExp; if ( !re ) { cal._parseRegExp = re = {}; } else { var reFormat = re[ format ]; if ( reFormat ) { return reFormat; } } // expand single digit formats, then escape regular expression characters. var expFormat = expandFormat( cal, format ).replace( /([\^\$\.\*\+\?\|\[\]\(\)\{\}])/g, "\\\\$1" ), regexp = [ "^" ], groups = [], index = 0, quoteCount = 0, tokenRegExp = getTokenRegExp(), match; // iterate through each date token found. while ( (match = tokenRegExp.exec(expFormat)) !== null ) { var preMatch = expFormat.slice( index, match.index ); index = tokenRegExp.lastIndex; // don't replace any matches that occur inside a string literal. quoteCount += appendPreOrPostMatch( preMatch, regexp ); if ( quoteCount % 2 ) { regexp.push( match[0] ); continue; } // add a regex group for the token. var m = match[ 0 ], len = m.length, add; switch ( m ) { case "dddd": case "ddd": case "MMMM": case "MMM": case "gg": case "g": add = "(\\D+)"; break; case "tt": case "t": add = "(\\D*)"; break; case "yyyy": case "fff": case "ff": case "f": add = "(\\d{" + len + "})"; break; case "dd": case "d": case "MM": case "M": case "yy": case "y": case "HH": case "H": case "hh": case "h": case "mm": case "m": case "ss": case "s": add = "(\\d\\d?)"; break; case "zzz": add = "([+-]?\\d\\d?:\\d{2})"; break; case "zz": case "z": add = "([+-]?\\d\\d?)"; break; case "/": add = "(\\" + cal[ "/" ] + ")"; break; default: throw "Invalid date format pattern \'" + m + "\'."; break; } if ( add ) { regexp.push( add ); } groups.push( match[0] ); } appendPreOrPostMatch( expFormat.slice(index), regexp ); regexp.push( "$" ); // allow whitespace to differ when matching formats. var regexpStr = regexp.join( "" ).replace( /\s+/g, "\\s+" ), parseRegExp = { "regExp": regexpStr, "groups": groups }; // cache the regex for this format. return re[ format ] = parseRegExp; }; outOfRange = function( value, low, high ) { return value < low || value > high; }; toUpper = function( value ) { // "he-IL" has non-breaking space in weekday names. return value.split( "\u00A0" ).join( " " ).toUpperCase(); }; toUpperArray = function( arr ) { var results = []; for ( var i = 0, l = arr.length; i < l; i++ ) { results[ i ] = toUpper( arr[i] ); } return results; }; parseExact = function( value, format, culture ) { // try to parse the date string by matching against the format string // while using the specified culture for date field names. value = trim( value ); var cal = culture.calendar, // convert date formats into regular expressions with groupings. // use the regexp to determine the input format and extract the date fields. parseInfo = getParseRegExp( cal, format ), match = new RegExp( parseInfo.regExp ).exec( value ); if ( match === null ) { return null; } // found a date format that matches the input. var groups = parseInfo.groups, era = null, year = null, month = null, date = null, weekDay = null, hour = 0, hourOffset, min = 0, sec = 0, msec = 0, tzMinOffset = null, pmHour = false; // iterate the format groups to extract and set the date fields. for ( var j = 0, jl = groups.length; j < jl; j++ ) { var matchGroup = match[ j + 1 ]; if ( matchGroup ) { var current = groups[ j ], clength = current.length, matchInt = parseInt( matchGroup, 10 ); switch ( current ) { case "dd": case "d": // Day of month. date = matchInt; // check that date is generally in valid range, also checking overflow below. if ( outOfRange(date, 1, 31) ) return null; break; case "MMM": case "MMMM": month = getMonthIndex( cal, matchGroup, clength === 3 ); if ( outOfRange(month, 0, 11) ) return null; break; case "M": case "MM": // Month. month = matchInt - 1; if ( outOfRange(month, 0, 11) ) return null; break; case "y": case "yy": case "yyyy": year = clength < 4 ? expandYear( cal, matchInt ) : matchInt; if ( outOfRange(year, 0, 9999) ) return null; break; case "h": case "hh": // Hours (12-hour clock). hour = matchInt; if ( hour === 12 ) hour = 0; if ( outOfRange(hour, 0, 11) ) return null; break; case "H": case "HH": // Hours (24-hour clock). hour = matchInt; if ( outOfRange(hour, 0, 23) ) return null; break; case "m": case "mm": // Minutes. min = matchInt; if ( outOfRange(min, 0, 59) ) return null; break; case "s": case "ss": // Seconds. sec = matchInt; if ( outOfRange(sec, 0, 59) ) return null; break; case "tt": case "t": // AM/PM designator. // see if it is standard, upper, or lower case PM. If not, ensure it is at least one of // the AM tokens. If not, fail the parse for this format. pmHour = cal.PM && ( matchGroup === cal.PM[0] || matchGroup === cal.PM[1] || matchGroup === cal.PM[2] ); if ( !pmHour && ( !cal.AM || ( matchGroup !== cal.AM[0] && matchGroup !== cal.AM[1] && matchGroup !== cal.AM[2] ) ) ) return null; break; case "f": // Deciseconds. case "ff": // Centiseconds. case "fff": // Milliseconds. msec = matchInt * Math.pow( 10, 3 - clength ); if ( outOfRange(msec, 0, 999) ) return null; break; case "ddd": // Day of week. case "dddd": // Day of week. weekDay = getDayIndex( cal, matchGroup, clength === 3 ); if ( outOfRange(weekDay, 0, 6) ) return null; break; case "zzz": // Time zone offset in +/- hours:min. var offsets = matchGroup.split( /:/ ); if ( offsets.length !== 2 ) return null; hourOffset = parseInt( offsets[0], 10 ); if ( outOfRange(hourOffset, -12, 13) ) return null; var minOffset = parseInt( offsets[1], 10 ); if ( outOfRange(minOffset, 0, 59) ) return null; tzMinOffset = ( hourOffset * 60 ) + ( startsWith(matchGroup, "-") ? -minOffset : minOffset ); break; case "z": case "zz": // Time zone offset in +/- hours. hourOffset = matchInt; if ( outOfRange(hourOffset, -12, 13) ) return null; tzMinOffset = hourOffset * 60; break; case "g": case "gg": var eraName = matchGroup; if ( !eraName || !cal.eras ) return null; eraName = trim( eraName.toLowerCase() ); for ( var i = 0, l = cal.eras.length; i < l; i++ ) { if ( eraName === cal.eras[i].name.toLowerCase() ) { era = i; break; } } // could not find an era with that name if ( era === null ) return null; break; } } } var result = new Date(), defaultYear, convert = cal.convert; defaultYear = convert ? convert.fromGregorian( result )[ 0 ] : result.getFullYear(); if ( year === null ) { year = defaultYear; } else if ( cal.eras ) { // year must be shifted to normal gregorian year // but not if year was not specified, its already normal gregorian // per the main if clause above. year += cal.eras[( era || 0 )].offset; } // set default day and month to 1 and January, so if unspecified, these are the defaults // instead of the current day/month. if ( month === null ) { month = 0; } if ( date === null ) { date = 1; } // now have year, month, and date, but in the culture's calendar. // convert to gregorian if necessary if ( convert ) { result = convert.toGregorian( year, month, date ); // conversion failed, must be an invalid match if ( result === null ) return null; } else { // have to set year, month and date together to avoid overflow based on current date. result.setFullYear( year, month, date ); // check to see if date overflowed for specified month (only checked 1-31 above). if ( result.getDate() !== date ) return null; // invalid day of week. if ( weekDay !== null && result.getDay() !== weekDay ) { return null; } } // if pm designator token was found make sure the hours fit the 24-hour clock. if ( pmHour && hour < 12 ) { hour += 12; } result.setHours( hour, min, sec, msec ); if ( tzMinOffset !== null ) { // adjust timezone to utc before applying local offset. var adjustedMin = result.getMinutes() - ( tzMinOffset + result.getTimezoneOffset() ); // Safari limits hours and minutes to the range of -127 to 127. We need to use setHours // to ensure both these fields will not exceed this range. adjustedMin will range // somewhere between -1440 and 1500, so we only need to split this into hours. result.setHours( result.getHours() + parseInt(adjustedMin / 60, 10), adjustedMin % 60 ); } return result; }; }()); parseNegativePattern = function( value, nf, negativePattern ) { var neg = nf[ "-" ], pos = nf[ "+" ], ret; switch ( negativePattern ) { case "n -": neg = " " + neg; pos = " " + pos; // fall through case "n-": if ( endsWith(value, neg) ) { ret = [ "-", value.substr(0, value.length - neg.length) ]; } else if ( endsWith(value, pos) ) { ret = [ "+", value.substr(0, value.length - pos.length) ]; } break; case "- n": neg += " "; pos += " "; // fall through case "-n": if ( startsWith(value, neg) ) { ret = [ "-", value.substr(neg.length) ]; } else if ( startsWith(value, pos) ) { ret = [ "+", value.substr(pos.length) ]; } break; case "(n)": if ( startsWith(value, "(") && endsWith(value, ")") ) { ret = [ "-", value.substr(1, value.length - 2) ]; } break; } return ret || [ "", value ]; }; // // public instance functions // Globalize.prototype.findClosestCulture = function( cultureSelector ) { return Globalize.findClosestCulture.call( this, cultureSelector ); }; Globalize.prototype.format = function( value, format, cultureSelector ) { return Globalize.format.call( this, value, format, cultureSelector ); }; Globalize.prototype.localize = function( key, cultureSelector ) { return Globalize.localize.call( this, key, cultureSelector ); }; Globalize.prototype.parseInt = function( value, radix, cultureSelector ) { return Globalize.parseInt.call( this, value, radix, cultureSelector ); }; Globalize.prototype.parseFloat = function( value, radix, cultureSelector ) { return Globalize.parseFloat.call( this, value, radix, cultureSelector ); }; Globalize.prototype.culture = function( cultureSelector ) { return Globalize.culture.call( this, cultureSelector ); }; // // public singleton functions // Globalize.addCultureInfo = function( cultureName, baseCultureName, info ) { var base = {}, isNew = false; if ( typeof cultureName !== "string" ) { // cultureName argument is optional string. If not specified, assume info is first // and only argument. Specified info deep-extends current culture. info = cultureName; cultureName = this.culture().name; base = this.cultures[ cultureName ]; } else if ( typeof baseCultureName !== "string" ) { // baseCultureName argument is optional string. If not specified, assume info is second // argument. Specified info deep-extends specified culture. // If specified culture does not exist, create by deep-extending default info = baseCultureName; isNew = ( this.cultures[ cultureName ] == null ); base = this.cultures[ cultureName ] || this.cultures[ "default" ]; } else { // cultureName and baseCultureName specified. Assume a new culture is being created // by deep-extending an specified base culture isNew = true; base = this.cultures[ baseCultureName ]; } this.cultures[ cultureName ] = extend(true, {}, base, info ); // Make the standard calendar the current culture if it's a new culture if ( isNew ) { this.cultures[ cultureName ].calendar = this.cultures[ cultureName ].calendars.standard; } }; Globalize.findClosestCulture = function( name ) { var match; if ( !name ) { return this.cultures[ this.cultureSelector ] || this.cultures[ "default" ]; } if ( typeof name === "string" ) { name = name.split( "," ); } if ( isArray(name) ) { var lang, cultures = this.cultures, list = name, i, l = list.length, prioritized = []; for ( i = 0; i < l; i++ ) { name = trim( list[i] ); var pri, parts = name.split( ";" ); lang = trim( parts[0] ); if ( parts.length === 1 ) { pri = 1; } else { name = trim( parts[1] ); if ( name.indexOf("q=") === 0 ) { name = name.substr( 2 ); pri = parseFloat( name ); pri = isNaN( pri ) ? 0 : pri; } else { pri = 1; } } prioritized.push({ lang: lang, pri: pri }); } prioritized.sort(function( a, b ) { return a.pri < b.pri ? 1 : -1; }); // exact match for ( i = 0; i < l; i++ ) { lang = prioritized[ i ].lang; match = cultures[ lang ]; if ( match ) { return match; } } // neutral language match for ( i = 0; i < l; i++ ) { lang = prioritized[ i ].lang; do { var index = lang.lastIndexOf( "-" ); if ( index === -1 ) { break; } // strip off the last part. e.g. en-US => en lang = lang.substr( 0, index ); match = cultures[ lang ]; if ( match ) { return match; } } while ( 1 ); } // last resort: match first culture using that language for ( i = 0; i < l; i++ ) { lang = prioritized[ i ].lang; for ( var cultureKey in cultures ) { var culture = cultures[ cultureKey ]; if ( culture.language == lang ) { return culture; } } } } else if ( typeof name === "object" ) { return name; } return match || null; }; Globalize.format = function( value, format, cultureSelector ) { culture = this.findClosestCulture( cultureSelector ); if ( value instanceof Date ) { value = formatDate( value, format, culture ); } else if ( typeof value === "number" ) { value = formatNumber( value, format, culture ); } return value; }; Globalize.localize = function( key, cultureSelector ) { return this.findClosestCulture( cultureSelector ).messages[ key ] || this.cultures[ "default" ].messages[ key ]; }; Globalize.parseDate = function( value, formats, culture ) { culture = this.findClosestCulture( culture ); var date, prop, patterns; if ( formats ) { if ( typeof formats === "string" ) { formats = [ formats ]; } if ( formats.length ) { for ( var i = 0, l = formats.length; i < l; i++ ) { var format = formats[ i ]; if ( format ) { date = parseExact( value, format, culture ); if ( date ) { break; } } } } } else { patterns = culture.calendar.patterns; for ( prop in patterns ) { date = parseExact( value, patterns[prop], culture ); if ( date ) { break; } } } return date || null; }; Globalize.parseInt = function( value, radix, cultureSelector ) { return truncate( Globalize.parseFloat(value, radix, cultureSelector) ); }; Globalize.parseFloat = function( value, radix, cultureSelector ) { // radix argument is optional if ( typeof radix !== "number" ) { cultureSelector = radix; radix = 10; } var culture = this.findClosestCulture( cultureSelector ); var ret = NaN, nf = culture.numberFormat; if ( value.indexOf(culture.numberFormat.currency.symbol) > -1 ) { // remove currency symbol value = value.replace( culture.numberFormat.currency.symbol, "" ); // replace decimal seperator value = value.replace( culture.numberFormat.currency["."], culture.numberFormat["."] ); } // trim leading and trailing whitespace value = trim( value ); // allow infinity or hexidecimal if ( regexInfinity.test(value) ) { ret = parseFloat( value ); } else if ( !radix && regexHex.test(value) ) { ret = parseInt( value, 16 ); } else { // determine sign and number var signInfo = parseNegativePattern( value, nf, nf.pattern[0] ), sign = signInfo[ 0 ], num = signInfo[ 1 ]; // #44 - try parsing as "(n)" if ( sign === "" && nf.pattern[0] !== "(n)" ) { signInfo = parseNegativePattern( value, nf, "(n)" ); sign = signInfo[ 0 ]; num = signInfo[ 1 ]; } // try parsing as "-n" if ( sign === "" && nf.pattern[0] !== "-n" ) { signInfo = parseNegativePattern( value, nf, "-n" ); sign = signInfo[ 0 ]; num = signInfo[ 1 ]; } sign = sign || "+"; // determine exponent and number var exponent, intAndFraction, exponentPos = num.indexOf( "e" ); if ( exponentPos < 0 ) exponentPos = num.indexOf( "E" ); if ( exponentPos < 0 ) { intAndFraction = num; exponent = null; } else { intAndFraction = num.substr( 0, exponentPos ); exponent = num.substr( exponentPos + 1 ); } // determine decimal position var integer, fraction, decSep = nf[ "." ], decimalPos = intAndFraction.indexOf( decSep ); if ( decimalPos < 0 ) { integer = intAndFraction; fraction = null; } else { integer = intAndFraction.substr( 0, decimalPos ); fraction = intAndFraction.substr( decimalPos + decSep.length ); } // handle groups (e.g. 1,000,000) var groupSep = nf[ "," ]; integer = integer.split( groupSep ).join( "" ); var altGroupSep = groupSep.replace( /\u00A0/g, " " ); if ( groupSep !== altGroupSep ) { integer = integer.split( altGroupSep ).join( "" ); } // build a natively parsable number string var p = sign + integer; if ( fraction !== null ) { p += "." + fraction; } if ( exponent !== null ) { // exponent itself may have a number patternd var expSignInfo = parseNegativePattern( exponent, nf, "-n" ); p += "e" + ( expSignInfo[0] || "+" ) + expSignInfo[ 1 ]; } if ( regexParseFloat.test(p) ) { ret = parseFloat( p ); } } return ret; }; Globalize.culture = function( cultureSelector ) { // setter if ( typeof cultureSelector !== "undefined" ) { this.cultureSelector = cultureSelector; } // getter return this.findClosestCulture( cultureSelector ) || this.culture[ "default" ]; }; }( this )); ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/external/jquery.bgiframe-2.1.2.js ================================================ /*! Copyright (c) 2010 Brandon Aaron (http://brandonaaron.net) * Licensed under the MIT License (LICENSE.txt). * * Version 2.1.2 */ (function($){ $.fn.bgiframe = ($.browser.msie && /msie 6\.0/i.test(navigator.userAgent) ? function(s) { s = $.extend({ top : 'auto', // auto == .currentStyle.borderTopWidth left : 'auto', // auto == .currentStyle.borderLeftWidth width : 'auto', // auto == offsetWidth height : 'auto', // auto == offsetHeight opacity : true, src : 'javascript:false;' }, s); var html = '' : ''); inst._keyEvent = false; return html; }, /* Generate the month and year header. */ _generateMonthYearHeader: function(inst, drawMonth, drawYear, minDate, maxDate, secondary, monthNames, monthNamesShort) { var changeMonth = this._get(inst, 'changeMonth'); var changeYear = this._get(inst, 'changeYear'); var showMonthAfterYear = this._get(inst, 'showMonthAfterYear'); var html = '
      '; var monthHtml = ''; // month selection if (secondary || !changeMonth) monthHtml += '' + monthNames[drawMonth] + ''; else { var inMinYear = (minDate && minDate.getFullYear() == drawYear); var inMaxYear = (maxDate && maxDate.getFullYear() == drawYear); monthHtml += ''; } if (!showMonthAfterYear) html += monthHtml + (secondary || !(changeMonth && changeYear) ? ' ' : ''); // year selection if ( !inst.yearshtml ) { inst.yearshtml = ''; if (secondary || !changeYear) html += '' + drawYear + ''; else { // determine range of years to display var years = this._get(inst, 'yearRange').split(':'); var thisYear = new Date().getFullYear(); var determineYear = function(value) { var year = (value.match(/c[+-].*/) ? drawYear + parseInt(value.substring(1), 10) : (value.match(/[+-].*/) ? thisYear + parseInt(value, 10) : parseInt(value, 10))); return (isNaN(year) ? thisYear : year); }; var year = determineYear(years[0]); var endYear = Math.max(year, determineYear(years[1] || '')); year = (minDate ? Math.max(year, minDate.getFullYear()) : year); endYear = (maxDate ? Math.min(endYear, maxDate.getFullYear()) : endYear); inst.yearshtml += ''; html += inst.yearshtml; inst.yearshtml = null; } } html += this._get(inst, 'yearSuffix'); if (showMonthAfterYear) html += (secondary || !(changeMonth && changeYear) ? ' ' : '') + monthHtml; html += '
      '; // Close datepicker_header return html; }, /* Adjust one of the date sub-fields. */ _adjustInstDate: function(inst, offset, period) { var year = inst.drawYear + (period == 'Y' ? offset : 0); var month = inst.drawMonth + (period == 'M' ? offset : 0); var day = Math.min(inst.selectedDay, this._getDaysInMonth(year, month)) + (period == 'D' ? offset : 0); var date = this._restrictMinMax(inst, this._daylightSavingAdjust(new Date(year, month, day))); inst.selectedDay = date.getDate(); inst.drawMonth = inst.selectedMonth = date.getMonth(); inst.drawYear = inst.selectedYear = date.getFullYear(); if (period == 'M' || period == 'Y') this._notifyChange(inst); }, /* Ensure a date is within any min/max bounds. */ _restrictMinMax: function(inst, date) { var minDate = this._getMinMaxDate(inst, 'min'); var maxDate = this._getMinMaxDate(inst, 'max'); var newDate = (minDate && date < minDate ? minDate : date); newDate = (maxDate && newDate > maxDate ? maxDate : newDate); return newDate; }, /* Notify change of month/year. */ _notifyChange: function(inst) { var onChange = this._get(inst, 'onChangeMonthYear'); if (onChange) onChange.apply((inst.input ? inst.input[0] : null), [inst.selectedYear, inst.selectedMonth + 1, inst]); }, /* Determine the number of months to show. */ _getNumberOfMonths: function(inst) { var numMonths = this._get(inst, 'numberOfMonths'); return (numMonths == null ? [1, 1] : (typeof numMonths == 'number' ? [1, numMonths] : numMonths)); }, /* Determine the current maximum date - ensure no time components are set. */ _getMinMaxDate: function(inst, minMax) { return this._determineDate(inst, this._get(inst, minMax + 'Date'), null); }, /* Find the number of days in a given month. */ _getDaysInMonth: function(year, month) { return 32 - this._daylightSavingAdjust(new Date(year, month, 32)).getDate(); }, /* Find the day of the week of the first of a month. */ _getFirstDayOfMonth: function(year, month) { return new Date(year, month, 1).getDay(); }, /* Determines if we should allow a "next/prev" month display change. */ _canAdjustMonth: function(inst, offset, curYear, curMonth) { var numMonths = this._getNumberOfMonths(inst); var date = this._daylightSavingAdjust(new Date(curYear, curMonth + (offset < 0 ? offset : numMonths[0] * numMonths[1]), 1)); if (offset < 0) date.setDate(this._getDaysInMonth(date.getFullYear(), date.getMonth())); return this._isInRange(inst, date); }, /* Is the given date in the accepted range? */ _isInRange: function(inst, date) { var minDate = this._getMinMaxDate(inst, 'min'); var maxDate = this._getMinMaxDate(inst, 'max'); return ((!minDate || date.getTime() >= minDate.getTime()) && (!maxDate || date.getTime() <= maxDate.getTime())); }, /* Provide the configuration settings for formatting/parsing. */ _getFormatConfig: function(inst) { var shortYearCutoff = this._get(inst, 'shortYearCutoff'); shortYearCutoff = (typeof shortYearCutoff != 'string' ? shortYearCutoff : new Date().getFullYear() % 100 + parseInt(shortYearCutoff, 10)); return {shortYearCutoff: shortYearCutoff, dayNamesShort: this._get(inst, 'dayNamesShort'), dayNames: this._get(inst, 'dayNames'), monthNamesShort: this._get(inst, 'monthNamesShort'), monthNames: this._get(inst, 'monthNames')}; }, /* Format the given date for display. */ _formatDate: function(inst, day, month, year) { if (!day) { inst.currentDay = inst.selectedDay; inst.currentMonth = inst.selectedMonth; inst.currentYear = inst.selectedYear; } var date = (day ? (typeof day == 'object' ? day : this._daylightSavingAdjust(new Date(year, month, day))) : this._daylightSavingAdjust(new Date(inst.currentYear, inst.currentMonth, inst.currentDay))); return this.formatDate(this._get(inst, 'dateFormat'), date, this._getFormatConfig(inst)); } }); /* * Bind hover events for datepicker elements. * Done via delegate so the binding only occurs once in the lifetime of the parent div. * Global instActive, set by _updateDatepicker allows the handlers to find their way back to the active picker. */ function bindHover(dpDiv) { var selector = 'button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a'; return dpDiv.delegate(selector, 'mouseout', function() { $(this).removeClass('ui-state-hover'); if (this.className.indexOf('ui-datepicker-prev') != -1) $(this).removeClass('ui-datepicker-prev-hover'); if (this.className.indexOf('ui-datepicker-next') != -1) $(this).removeClass('ui-datepicker-next-hover'); }) .delegate(selector, 'mouseover', function(){ if (!$.datepicker._isDisabledDatepicker( instActive.inline ? dpDiv.parent()[0] : instActive.input[0])) { $(this).parents('.ui-datepicker-calendar').find('a').removeClass('ui-state-hover'); $(this).addClass('ui-state-hover'); if (this.className.indexOf('ui-datepicker-prev') != -1) $(this).addClass('ui-datepicker-prev-hover'); if (this.className.indexOf('ui-datepicker-next') != -1) $(this).addClass('ui-datepicker-next-hover'); } }); } /* jQuery extend now ignores nulls! */ function extendRemove(target, props) { $.extend(target, props); for (var name in props) if (props[name] == null || props[name] == undefined) target[name] = props[name]; return target; }; /* Determine whether an object is an array. */ function isArray(a) { return (a && (($.browser.safari && typeof a == 'object' && a.length) || (a.constructor && a.constructor.toString().match(/\Array\(\)/)))); }; /* Invoke the datepicker functionality. @param options string - a command, optionally followed by additional parameters or Object - settings for attaching new datepicker functionality @return jQuery object */ $.fn.datepicker = function(options){ /* Verify an empty collection wasn't passed - Fixes #6976 */ if ( !this.length ) { return this; } /* Initialise the date picker. */ if (!$.datepicker.initialized) { $(document).mousedown($.datepicker._checkExternalClick). find('body').append($.datepicker.dpDiv); $.datepicker.initialized = true; } var otherArgs = Array.prototype.slice.call(arguments, 1); if (typeof options == 'string' && (options == 'isDisabled' || options == 'getDate' || options == 'widget')) return $.datepicker['_' + options + 'Datepicker']. apply($.datepicker, [this[0]].concat(otherArgs)); if (options == 'option' && arguments.length == 2 && typeof arguments[1] == 'string') return $.datepicker['_' + options + 'Datepicker']. apply($.datepicker, [this[0]].concat(otherArgs)); return this.each(function() { typeof options == 'string' ? $.datepicker['_' + options + 'Datepicker']. apply($.datepicker, [this].concat(otherArgs)) : $.datepicker._attachDatepicker(this, options); }); }; $.datepicker = new Datepicker(); // singleton instance $.datepicker.initialized = false; $.datepicker.uuid = new Date().getTime(); $.datepicker.version = "@VERSION"; // Workaround for #4055 // Add another global to avoid noConflict issues with inline event handlers window['DP_jQuery_' + dpuuid] = $; })(jQuery); ================================================ FILE: src/extensions/default/JavaScriptQuickEdit/unittest-files/jquery-ui/ui/jquery.ui.dialog.js ================================================ /*! * jQuery UI Dialog @VERSION * * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * * http://docs.jquery.com/UI/Dialog * * Depends: * jquery.ui.core.js * jquery.ui.widget.js * jquery.ui.button.js * jquery.ui.draggable.js * jquery.ui.mouse.js * jquery.ui.position.js * jquery.ui.resizable.js */ (function( $, undefined ) { var uiDialogClasses = "ui-dialog ui-widget ui-widget-content ui-corner-all ", sizeRelatedOptions = { buttons: true, height: true, maxHeight: true, maxWidth: true, minHeight: true, minWidth: true, width: true }, resizableRelatedOptions = { maxHeight: true, maxWidth: true, minHeight: true, minWidth: true }; $.widget("ui.dialog", { version: "@VERSION", options: { autoOpen: true, buttons: {}, closeOnEscape: true, closeText: "close", dialogClass: "", draggable: true, hide: null, height: "auto", maxHeight: false, maxWidth: false, minHeight: 150, minWidth: 150, modal: false, position: { my: "center", at: "center", of: window, collision: "fit", // ensure that the titlebar is never outside the document using: function( pos ) { var topOffset = $( this ).css( pos ).offset().top; if ( topOffset < 0 ) { $( this ).css( "top", pos.top - topOffset ); } } }, resizable: true, show: null, stack: true, title: "", width: 300, zIndex: 1000 }, _create: function() { this.originalTitle = this.element.attr( "title" ); // #5742 - .attr() might return a DOMElement if ( typeof this.originalTitle !== "string" ) { this.originalTitle = ""; } this.oldPosition = { parent: this.element.parent(), index: this.element.parent().children().index( this.element ) }; this.options.title = this.options.title || this.originalTitle; var that = this, options = this.options, title = options.title || " ", uiDialog = ( this.uiDialog = $( "
      " ) ) .addClass( uiDialogClasses + options.dialogClass ) .css({ display: "none", outline: 0, // TODO: move to stylesheet zIndex: options.zIndex }) // setting tabIndex makes the div focusable .attr( "tabIndex", -1) .keydown(function( event ) { if ( options.closeOnEscape && !event.isDefaultPrevented() && event.keyCode && event.keyCode === $.ui.keyCode.ESCAPE ) { that.close( event ); event.preventDefault(); } }) .mousedown(function( event ) { that.moveToTop( false, event ); }) .appendTo( "body" ), uiDialogContent = this.element .show() .removeAttr( "title" ) .addClass( "ui-dialog-content ui-widget-content" ) .appendTo( uiDialog ), uiDialogTitlebar = ( this.uiDialogTitlebar = $( "
      " ) ) .addClass( "ui-dialog-titlebar ui-widget-header " + "ui-corner-all ui-helper-clearfix" ) .prependTo( uiDialog ), uiDialogTitlebarClose = $( "" ) .addClass( "ui-dialog-titlebar-close ui-corner-all" ) .attr( "role", "button" ) .click(function( event ) { event.preventDefault(); that.close( event ); }) .appendTo( uiDialogTitlebar ), uiDialogTitlebarCloseText = ( this.uiDialogTitlebarCloseText = $( "" ) ) .addClass( "ui-icon ui-icon-closethick" ) .text( options.closeText ) .appendTo( uiDialogTitlebarClose ), uiDialogTitle = $( "" ) .uniqueId() .addClass( "ui-dialog-title" ) .html( title ) .prependTo( uiDialogTitlebar ), uiDialogButtonPane = ( this.uiDialogButtonPane = $( "
      " ) ) .addClass( "ui-dialog-buttonpane ui-widget-content ui-helper-clearfix" ), uiButtonSet = ( this.uiButtonSet = $( "
      " ) ) .addClass( "ui-dialog-buttonset" ) .appendTo( uiDialogButtonPane ); uiDialog.attr({ role: "dialog", "aria-labelledby": uiDialogTitle.attr( "id" ) }); uiDialogTitlebar.find( "*" ).add( uiDialogTitlebar ).disableSelection(); this._hoverable( uiDialogTitlebarClose ); this._focusable( uiDialogTitlebarClose ); if ( options.draggable && $.fn.draggable ) { this._makeDraggable(); } if ( options.resizable && $.fn.resizable ) { this._makeResizable(); } this._createButtons( options.buttons ); this._isOpen = false; if ( $.fn.bgiframe ) { uiDialog.bgiframe(); } }, _init: function() { if ( this.options.autoOpen ) { this.open(); } }, _destroy: function() { var next, oldPosition = this.oldPosition; if ( this.overlay ) { this.overlay.destroy(); } this.uiDialog.hide(); this.element .removeClass( "ui-dialog-content ui-widget-content" ) .hide() .appendTo( "body" ); this.uiDialog.remove(); if ( this.originalTitle ) { this.element.attr( "title", this.originalTitle ); } next = oldPosition.parent.children().eq( oldPosition.index ); if ( next.length ) { next.before( this.element ); } else { oldPosition.parent.append( this.element ); } }, widget: function() { return this.uiDialog; }, close: function( event ) { var that = this, maxZ, thisZ; if ( !this._isOpen ) { return; } if ( false === this._trigger( "beforeClose", event ) ) { return; } this._isOpen = false; if ( this.overlay ) { this.overlay.destroy(); } this.uiDialog.unbind( "keypress.ui-dialog" ); if ( this.options.hide ) { this.uiDialog.hide( this.options.hide, function() { that._trigger( "close", event ); }); } else { this.uiDialog.hide(); this._trigger( "close", event ); } $.ui.dialog.overlay.resize(); // adjust the maxZ to allow other modal dialogs to continue to work (see #4309) if ( this.options.modal ) { maxZ = 0; $( ".ui-dialog" ).each(function() { if ( this !== that.uiDialog[0] ) { thisZ = $( this ).css( "z-index" ); if ( !isNaN( thisZ ) ) { maxZ = Math.max( maxZ, thisZ ); } } }); $.ui.dialog.maxZ = maxZ; } return this; }, isOpen: function() { return this._isOpen; }, // the force parameter allows us to move modal dialogs to their correct // position on open moveToTop: function( force, event ) { var options = this.options, saveScroll; if ( ( options.modal && !force ) || ( !options.stack && !options.modal ) ) { return this._trigger( "focus", event ); } if ( options.zIndex > $.ui.dialog.maxZ ) { $.ui.dialog.maxZ = options.zIndex; } if ( this.overlay ) { $.ui.dialog.maxZ += 1; $.ui.dialog.overlay.maxZ = $.ui.dialog.maxZ; this.overlay.$el.css( "z-index", $.ui.dialog.overlay.maxZ ); } // Save and then restore scroll // Opera 9.5+ resets when parent z-index is changed. // http://bugs.jqueryui.com/ticket/3193 saveScroll = { scrollTop: this.element.scrollTop(), scrollLeft: this.element.scrollLeft() }; $.ui.dialog.maxZ += 1; this.uiDialog.css( "z-index", $.ui.dialog.maxZ ); this.element.attr( saveScroll ); this._trigger( "focus", event ); return this; }, open: function() { if ( this._isOpen ) { return; } var hasFocus, options = this.options, uiDialog = this.uiDialog; this._size(); this._position( options.position ); uiDialog.show( options.show ); this.overlay = options.modal ? new $.ui.dialog.overlay( this ) : null; this.moveToTop( true ); // prevent tabbing out of modal dialogs if ( options.modal ) { uiDialog.bind( "keydown.ui-dialog", function( event ) { if ( event.keyCode !== $.ui.keyCode.TAB ) { return; } var tabbables = $( ":tabbable", this ), first = tabbables.filter( ":first" ), last = tabbables.filter( ":last" ); if ( event.target === last[0] && !event.shiftKey ) { first.focus( 1 ); return false; } else if ( event.target === first[0] && event.shiftKey ) { last.focus( 1 ); return false; } }); } // set focus to the first tabbable element in the content area or the first button // if there are no tabbable elements, set focus on the dialog itself hasFocus = this.element.find( ":tabbable" ); if ( !hasFocus.length ) { hasFocus = this.uiDialogButtonPane.find( ":tabbable" ); if ( !hasFocus.length ) { hasFocus = uiDialog; } } hasFocus.eq( 0 ).focus(); this._isOpen = true; this._trigger( "open" ); return this; }, _createButtons: function( buttons ) { var uiDialogButtonPane, uiButtonSet, that = this, hasButtons = false; // if we already have a button pane, remove it this.uiDialogButtonPane.remove(); this.uiButtonSet.empty(); if ( typeof buttons === "object" && buttons !== null ) { $.each( buttons, function() { return !(hasButtons = true); }); } if ( hasButtons ) { $.each( buttons, function( name, props ) { props = $.isFunction( props ) ? { click: props, text: name } : props; var button = $( "