Repository: pythonanywhere/dirigible-spreadsheet Branch: master Commit: c771e9a39170 Files: 403 Total size: 3.2 MB Directory structure: gitextract_vy1u92c1/ ├── .gitignore ├── LICENSE.md ├── README.md ├── dirigible/ │ ├── .gitignore │ ├── dirigible/ │ │ ├── __init__.py │ │ ├── settings.py │ │ ├── test_utils.py │ │ ├── urls.py │ │ └── wsgi.py │ ├── featured_sheet/ │ │ ├── __init__.py │ │ ├── admin.py │ │ ├── models.py │ │ ├── templates/ │ │ │ └── featured_sheets.html │ │ ├── tests/ │ │ │ ├── __init__.py │ │ │ └── test_models.py │ │ └── views.py │ ├── feedback/ │ │ ├── __init__.py │ │ ├── models.py │ │ ├── tests/ │ │ │ ├── __init__.py │ │ │ └── test_views.py │ │ ├── urls.py │ │ └── views.py │ ├── fts/ │ │ ├── __init__.py │ │ ├── screendumps/ │ │ │ └── placeholder │ │ └── tests/ │ │ ├── __init__.py │ │ ├── functionaltest.py │ │ ├── test_2521_CodeEditor.py │ │ ├── test_2525_LoginLogout.py │ │ ├── test_2528_CreateEditSheet.py │ │ ├── test_2529_HighlightErrorsInCells.py │ │ ├── test_2531_DifficultStuffInCells.py │ │ ├── test_2532_LambdasInCells.py │ │ ├── test_2533_Numpy.py │ │ ├── test_2534_JsonWorksheets.py │ │ ├── test_2535_RunWorksheetSerial.py │ │ ├── test_2536_ParallelFormulaExecution.py │ │ ├── test_2537_ErrorsInConsole.py │ │ ├── test_2538_ShowStdoutInConsole.py │ │ ├── test_2540_FrontPage.py │ │ ├── test_2544_403_404_and_500_pages.py │ │ ├── test_2545_PageResizeBehaviour.py │ │ ├── test_2546_ListSheetsOnDashboard.py │ │ ├── test_2547_EnterDataQuickly.py │ │ ├── test_2548_UserCode.py │ │ ├── test_2549_InterruptedRecalculations.py │ │ ├── test_2550_EditableSheetName.py │ │ ├── test_2554_SlicingInFormulae.py │ │ ├── test_2556_BrokenUserCode.py │ │ ├── test_2557_ClickAwaySavesUsercode.py │ │ ├── test_2558_MoreCellsByDefault.py │ │ ├── test_2559_FitEditorToCells.py │ │ ├── test_2562_ErrorInCellShouldBeClearedByConstants.py │ │ ├── test_2565_JSONAPIAuth.py │ │ ├── test_2571_DocumentationAndBlogLinks.py │ │ ├── test_2577_SaveColumnWidths.py │ │ ├── test_2581_FormulaBar.py │ │ ├── test_2582_ReferencingEmptyCell.py │ │ ├── test_2592_Cut_Copy_Paste_Within_Dirigible.py │ │ ├── test_2595_Spinner.py │ │ ├── test_2597_CapRecalcTime.py │ │ ├── test_2601_UndefinedShouldBeAvailableToUsercode.py │ │ ├── test_2602_SheetPageShouldDisplayBeforeFirstRecalcComplete.py │ │ ├── test_2603_WorksheetsMayOnlyContainCells.py │ │ ├── test_2616_RootPageIsDashboard.py │ │ ├── test_2621_CanSaveSheetsWithLotsOfFormulae.py │ │ ├── test_2622_CellRanges.py │ │ ├── test_2631_BlogRedirect.py │ │ ├── test_2633_CursorKeysMoveAroundGrid.py │ │ ├── test_2635_SheetNameSelectedOnEdit.py │ │ ├── test_2639_SciPy_and_MpMath.py │ │ ├── test_2642_RecalcTimesInConsole.py │ │ ├── test_2644_AdminOmniscience.py │ │ ├── test_2650_UsercodeSandbox.py │ │ ├── test_2651_SaveSheetNameOnBlur.py │ │ ├── test_2652_CommitCellOnBlur.py │ │ ├── test_2653_UsernameFocusedOnLoginPage.py │ │ ├── test_2654_CtrlSSavesUsercode.py │ │ ├── test_2678_GlobalStateNotShared.py │ │ ├── test_2682_CellAccessUsingA1.py │ │ ├── test_2685_ChangePassword.py │ │ ├── test_2689_DontClearCellEditorWhenRecalcsHitClient.py │ │ ├── test_2690_FocusShouldStartInGrid.py │ │ ├── test_2691_AllowSettingValuesFromUsercodeBeforeLoadConstants.py │ │ ├── test_2701_NameResolutionWorks.py │ │ ├── test_2702_HttpsInChrootJail.py │ │ ├── test_2704_OldStyleClassesInTheGrid.py │ │ ├── test_2711_ImportExcel.py │ │ ├── test_2712_ImportCSV.py │ │ ├── test_2726_FormulaBarTextSelection.py │ │ ├── test_2734_ClearCells.py │ │ ├── test_2735_CtrlKeysArePassedOnToBrowser.py │ │ ├── test_2741_Xlrd.py │ │ ├── test_2749_DisallowArbitraryKeysForWorksheet.py │ │ ├── test_2751_UsefulModules.py │ │ ├── test_2758_LoadGridDataOnDemand.py │ │ ├── test_2762_PythonConversion.py │ │ ├── test_2770_ClearDependentCellErrors.py │ │ ├── test_2774_ExportCSV.py │ │ ├── test_2781_FormulaAndFormattedValueMustBeStrings.py │ │ ├── test_2787_SignUp.py │ │ ├── test_2789_ErrorConsoleHTMLEscape.py │ │ ├── test_2795_Rewrite_Formulae_during_Cut_and_Paste.py │ │ ├── test_2799_FillDownDuringPaste.py │ │ ├── test_2812_CutCopyPasteInEditMode.py │ │ ├── test_2814_PublicWorksheets.py │ │ ├── test_2828_GridShouldNotStealFocusOnRecalc.py │ │ ├── test_2839_CutCopyPasteButtons.py │ │ ├── test_2844_CantMakeRowHeaderActive.py │ │ ├── test_2848_WorksheetBounds.py │ │ ├── test_2862_CopyAndPasteFormulaWithErrors.py │ │ ├── test_2872_CellTooltips.py │ │ ├── test_2873_IE_Warning.py │ │ ├── test_2884_FeedbackForm.py │ │ └── test_data/ │ │ ├── T2711-badly-named-png.xls │ │ ├── T2711-import-excel.xls │ │ ├── csv_file.csv │ │ ├── excel_generated_csv.csv │ │ ├── expected_csv_file.csv │ │ ├── expected_unicode_csv.csv │ │ ├── japanese.csv │ │ └── public_sheet_csv_file.csv │ ├── info_pages/ │ │ ├── __init__.py │ │ ├── migrations/ │ │ │ └── __init__.py │ │ ├── models.py │ │ ├── templates/ │ │ │ ├── non_logged_in_front_page.html │ │ │ ├── oss.html │ │ │ └── video.html │ │ ├── tests/ │ │ │ ├── __init__.py │ │ │ └── test_views.py │ │ └── views.py │ ├── manage.py │ ├── registration/ │ │ ├── __init__.py │ │ ├── admin.py │ │ ├── forms.py │ │ ├── locale/ │ │ │ ├── ar/ │ │ │ │ └── LC_MESSAGES/ │ │ │ │ ├── django.mo │ │ │ │ └── django.po │ │ │ ├── bg/ │ │ │ │ └── LC_MESSAGES/ │ │ │ │ ├── django.mo │ │ │ │ └── django.po │ │ │ ├── de/ │ │ │ │ └── LC_MESSAGES/ │ │ │ │ ├── django.mo │ │ │ │ └── django.po │ │ │ ├── el/ │ │ │ │ └── LC_MESSAGES/ │ │ │ │ ├── django.mo │ │ │ │ └── django.po │ │ │ ├── en/ │ │ │ │ └── LC_MESSAGES/ │ │ │ │ ├── django.mo │ │ │ │ └── django.po │ │ │ ├── es/ │ │ │ │ └── LC_MESSAGES/ │ │ │ │ ├── django.mo │ │ │ │ └── django.po │ │ │ ├── es_AR/ │ │ │ │ └── LC_MESSAGES/ │ │ │ │ ├── django.mo │ │ │ │ └── django.po │ │ │ ├── fr/ │ │ │ │ └── LC_MESSAGES/ │ │ │ │ ├── django.mo │ │ │ │ └── django.po │ │ │ ├── he/ │ │ │ │ └── LC_MESSAGES/ │ │ │ │ ├── django.mo │ │ │ │ └── django.po │ │ │ ├── it/ │ │ │ │ └── LC_MESSAGES/ │ │ │ │ ├── django.mo │ │ │ │ └── django.po │ │ │ ├── ja/ │ │ │ │ └── LC_MESSAGES/ │ │ │ │ ├── django.mo │ │ │ │ └── django.po │ │ │ ├── nl/ │ │ │ │ └── LC_MESSAGES/ │ │ │ │ ├── django.mo │ │ │ │ └── django.po │ │ │ ├── pl/ │ │ │ │ └── LC_MESSAGES/ │ │ │ │ ├── django.mo │ │ │ │ └── django.po │ │ │ ├── pt_BR/ │ │ │ │ └── LC_MESSAGES/ │ │ │ │ ├── django.mo │ │ │ │ └── django.po │ │ │ ├── ru/ │ │ │ │ └── LC_MESSAGES/ │ │ │ │ ├── django.mo │ │ │ │ └── django.po │ │ │ ├── sr/ │ │ │ │ └── LC_MESSAGES/ │ │ │ │ ├── django.mo │ │ │ │ └── django.po │ │ │ ├── sv/ │ │ │ │ └── LC_MESSAGES/ │ │ │ │ ├── django.mo │ │ │ │ └── django.po │ │ │ ├── zh_CN/ │ │ │ │ └── LC_MESSAGES/ │ │ │ │ ├── django.mo │ │ │ │ └── django.po │ │ │ └── zh_TW/ │ │ │ └── LC_MESSAGES/ │ │ │ ├── django.mo │ │ │ └── django.po │ │ ├── management/ │ │ │ ├── __init__.py │ │ │ └── commands/ │ │ │ ├── __init__.py │ │ │ └── cleanupregistration.py │ │ ├── migrations/ │ │ │ ├── 0001_initial.py │ │ │ └── __init__.py │ │ ├── models.py │ │ ├── tests.py │ │ ├── urls.py │ │ └── views.py │ ├── shared/ │ │ ├── __init__.py │ │ ├── models.py │ │ ├── static/ │ │ │ ├── ace/ │ │ │ │ ├── ace-uncompressed.js │ │ │ │ ├── ace.js │ │ │ │ ├── cockpit-uncompressed.js │ │ │ │ ├── cockpit.js │ │ │ │ ├── mode-python.js │ │ │ │ └── worker-javascript.js │ │ │ ├── dirigible/ │ │ │ │ ├── examples/ │ │ │ │ │ └── pricelist-json-api-demo.html │ │ │ │ ├── images/ │ │ │ │ │ └── toolbar/ │ │ │ │ │ └── api_button_disabled.pdn │ │ │ │ ├── scripts/ │ │ │ │ │ ├── cell_editor.js │ │ │ │ │ ├── console_view.js │ │ │ │ │ ├── dialogs.js │ │ │ │ │ ├── editor_commands.js │ │ │ │ │ ├── feedback_dialog.js │ │ │ │ │ ├── grid_commands.js │ │ │ │ │ ├── grid_content_converter.js │ │ │ │ │ ├── grid_interaction_handler.js │ │ │ │ │ ├── grid_remote_model.js │ │ │ │ │ ├── grid_view.js │ │ │ │ │ ├── htmlescape.js │ │ │ │ │ ├── keyboard_cellrange_selector.js │ │ │ │ │ ├── page_commands.js │ │ │ │ │ ├── page_interaction_handler.js │ │ │ │ │ ├── page_view.js │ │ │ │ │ ├── security_settings.js │ │ │ │ │ ├── selection_model.js │ │ │ │ │ ├── sheet_page_utils.js │ │ │ │ │ ├── toolbar_interaction_handler.js │ │ │ │ │ └── usercode_view.js │ │ │ │ ├── styles/ │ │ │ │ │ ├── base.css │ │ │ │ │ ├── coming_soon_page.css │ │ │ │ │ ├── contact.css │ │ │ │ │ ├── error.css │ │ │ │ │ ├── index.css │ │ │ │ │ ├── info_page.css │ │ │ │ │ ├── login.css │ │ │ │ │ ├── non_sheet_page.css │ │ │ │ │ ├── pricing.css │ │ │ │ │ ├── registration.css │ │ │ │ │ ├── sheet_page.css │ │ │ │ │ ├── user_page.css │ │ │ │ │ └── video.css │ │ │ │ └── tests/ │ │ │ │ ├── cell_editor_test.html │ │ │ │ ├── console_view_test.html │ │ │ │ ├── dialogs_test.html │ │ │ │ ├── editor_commands_test.html │ │ │ │ ├── feedback_dialog_test.html │ │ │ │ ├── grid_commands_test.html │ │ │ │ ├── grid_content_converter_test.html │ │ │ │ ├── grid_interaction_handler_test.html │ │ │ │ ├── grid_remote_model_test.html │ │ │ │ ├── grid_view_test.html │ │ │ │ ├── htmlescape_test.html │ │ │ │ ├── jsmock.js │ │ │ │ ├── logger.css │ │ │ │ ├── page_commands_test.html │ │ │ │ ├── page_interaction_handler_test.html │ │ │ │ ├── page_view_test.html │ │ │ │ ├── security_settings_test.html │ │ │ │ ├── selection_model_test.html │ │ │ │ ├── sheet_page_utils_test.html │ │ │ │ ├── test_utils.js │ │ │ │ ├── test_utils_test.html │ │ │ │ ├── testlogger.css │ │ │ │ ├── toolbar_interaction_handler_test.html │ │ │ │ ├── usercode_view_test.html │ │ │ │ ├── yuirunner.js │ │ │ │ └── yuitest/ │ │ │ │ └── yuitest-combo.js │ │ │ ├── jquery/ │ │ │ │ ├── jeip.js │ │ │ │ ├── jquery-ui-1.8.10.custom.css │ │ │ │ ├── jquery.ajaxq-0.0.1.js │ │ │ │ └── jquery.cookie.js │ │ │ ├── json/ │ │ │ │ └── json2.js │ │ │ ├── robots.txt │ │ │ ├── slickgrid/ │ │ │ │ ├── MIT-LICENSE.txt │ │ │ │ ├── slick.cellrangedecorator.js │ │ │ │ ├── slick.cellrangeselector.js │ │ │ │ ├── slick.cellselectionmodel.js │ │ │ │ ├── slick.core.js │ │ │ │ ├── slick.editors.js │ │ │ │ ├── slick.grid.css │ │ │ │ └── slick.grid.js │ │ │ └── splitter/ │ │ │ └── splitter.js │ │ ├── templates/ │ │ │ ├── 403.html │ │ │ ├── 404.html │ │ │ ├── 500.html │ │ │ ├── base.html │ │ │ ├── error_page.html │ │ │ ├── footer_links_include.html │ │ │ ├── header_links_include.html │ │ │ ├── info_page.html │ │ │ └── non_sheet_page_small_logo.html │ │ ├── tests/ │ │ │ ├── __init__.py │ │ │ └── test_views.py │ │ └── views.py │ ├── sheet/ │ │ ├── __init__.py │ │ ├── admin.py │ │ ├── calculate.py │ │ ├── cell.py │ │ ├── cell_range.py │ │ ├── clipboard.py │ │ ├── dependency_graph.py │ │ ├── dirigible_datetime.py │ │ ├── errors.py │ │ ├── eval_constant.py │ │ ├── forms.py │ │ ├── formula_interpreter.py │ │ ├── importer.py │ │ ├── migrations/ │ │ │ ├── 0001_initial.py │ │ │ └── __init__.py │ │ ├── models.py │ │ ├── parser/ │ │ │ ├── __init__.py │ │ │ ├── fl_cell_range_parse_node.py │ │ │ ├── fl_cell_reference_parse_node.py │ │ │ ├── fl_column_reference_parse_node.py │ │ │ ├── fl_named_column_reference_parse_node.py │ │ │ ├── fl_named_row_reference_parse_node.py │ │ │ ├── fl_reference_parse_node.py │ │ │ ├── fl_row_reference_parse_node.py │ │ │ ├── grammar.py │ │ │ ├── parse_node.py │ │ │ ├── parse_node_constructors.py │ │ │ ├── parser.py │ │ │ ├── parsetab.py │ │ │ └── tokens.py │ │ ├── rewrite_formula_offset_cell_references.py │ │ ├── sheet.py │ │ ├── templates/ │ │ │ ├── export_csv_error.html │ │ │ ├── import_csv_error.html │ │ │ ├── import_xls_error.html │ │ │ └── sheet_page.html │ │ ├── tests/ │ │ │ ├── __init__.py │ │ │ ├── parser/ │ │ │ │ ├── __init__.py │ │ │ │ ├── test_fl_cell_range_parse_node.py │ │ │ │ ├── test_fl_cell_reference_parse_node.py │ │ │ │ ├── test_fl_coloumn_reference_parse_node.py │ │ │ │ ├── test_fl_named_column_reference_parse_node.py │ │ │ │ ├── test_fl_named_row_reference_parse_node.py │ │ │ │ ├── test_fl_reference_parse_node.py │ │ │ │ ├── test_fl_row_reference_parse_node.py │ │ │ │ ├── test_parse_node.py │ │ │ │ ├── test_parse_node_constructors.py │ │ │ │ └── test_parser.py │ │ │ ├── test_calculate.py │ │ │ ├── test_cell.py │ │ │ ├── test_cell_range.py │ │ │ ├── test_clipboard.py │ │ │ ├── test_dependency_graph.py │ │ │ ├── test_dirigible_datetime.py │ │ │ ├── test_errors.py │ │ │ ├── test_eval_constant.py │ │ │ ├── test_forms.py │ │ │ ├── test_formula_interpreter.py │ │ │ ├── test_importer.py │ │ │ ├── test_rewrite_formula_offset_cell_references.py │ │ │ ├── test_sheet.py │ │ │ ├── test_ui_jsonifier.py │ │ │ ├── test_views.py │ │ │ ├── test_views_api_0_1.py │ │ │ ├── test_worksheet.py │ │ │ └── utils/ │ │ │ ├── __init__.py │ │ │ ├── test_cell_name_utils.py │ │ │ ├── test_interruptable_thread.py │ │ │ └── test_string_utils.py │ │ ├── ui_jsonifier.py │ │ ├── urls.py │ │ ├── urls_api_0_1.py │ │ ├── utils/ │ │ │ ├── __init__.py │ │ │ ├── cell_name_utils.py │ │ │ ├── interruptable_thread.py │ │ │ └── string_utils.py │ │ ├── views.py │ │ ├── views_api_0_1.py │ │ └── worksheet.py │ └── user/ │ ├── __init__.py │ ├── admin.py │ ├── forms.py │ ├── migrations/ │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ ├── signup_urls.py │ ├── templates/ │ │ ├── login.html │ │ ├── registration/ │ │ │ ├── activate.html │ │ │ ├── activation_email.txt │ │ │ ├── activation_email_subject.txt │ │ │ ├── registration_complete.html │ │ │ └── registration_form.html │ │ ├── user_page.html │ │ └── welcome_email.txt │ ├── tests/ │ │ ├── __init__.py │ │ ├── test_forms.py │ │ ├── test_models.py │ │ └── test_views.py │ ├── urls.py │ └── views.py ├── documentation/ │ ├── .gitignore │ ├── BeautifulSoup.py │ ├── Makefile │ ├── _static/ │ │ └── file-to-force-git-to-keep-empty-dir │ ├── _templates/ │ │ └── layout.html │ ├── builtins.rst │ ├── conf.py │ ├── dirigible-theme/ │ │ ├── layout.html │ │ ├── static/ │ │ │ └── dirigible-style.css │ │ └── theme.conf │ ├── fl-python-differences.rst │ ├── import_export.rst │ ├── index.rst │ ├── json_api.rst │ ├── overview.rst │ ├── public_sheets.rst │ ├── python-modules.rst │ ├── spreadsheet-functions.rsl │ ├── spreadsheet-functions.rst │ ├── talk.md │ ├── talk_example_sheets.json │ ├── tutorial01.rst │ ├── tutorial02.rst │ ├── tutorial03.rst │ └── tutorial04.rst └── requirements.txt ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ *.pyc python/dirigible/dirigible.db ================================================ FILE: LICENSE.md ================================================ The MIT License (MIT) Copyright (c) 2014 Resolver Systems Ltd, PythonAnywhere LLP 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 ================================================ Dirigible, the web-based Pythonic Spreadsheet ============================================= This is the source code from the end-of-lifed https://www.projectdirigible.com project, preserved for posterity and the curious Installation instructions ------------------------- cd dirigible pip install -r requirements.txt python manage.py migrate python manage.py createsuperuser # make yourself a user account. python manage.py runserver And visit http://localhost:8000 Security -------- This version of Dirigible has absolutely no security, so bewarned, before you put this on a public server -- anyone that logs in has the full access permissions of whichever user is running django, and you can use Python to do pretty much whatever you want! Check out the chroot_recalculation branch for a slightly more secure approach Some minimal context -------------------- (Probably best to play around with the spreadsheet a bit before reading this guide, to get an idea of what is possible. Try creating a couple of formulae with calculations, and maybe using a user-defined function from the usercode panel) * A Dirigible spreadsheet is "just" a python program, which is visible in the usercode panel on the right. * Recalculating the spreadsheet means executing that code, including two very important built-in functions: `load_constants` and `evaluate_formulae`. In between those two functions, the user can add their own arbitrary code. One global object is accessible, the `worksheet` ([source](https://github.com/pythonanywhere/dirigible-spreadsheet/blob/master/dirigible/sheet/worksheet.py)) A worksheet is compose of cells (it is in fact a dictionary, whose keys are the location, expressed as a tuple of column,row, and whose values are cell objects) A cell ([source](https://github.com/pythonanywhere/dirigible-spreadsheet/blob/master/dirigible/sheet/cell.py)) has two key attributes: - its `formula`, which may just be a constant like "hello" or "12,3", or a formula, like =A1+B1 - its `value`, which is the result of evaluating the formula. If the formula is a constant, its value is that constant. If it's a real formula, then it will be evaluated as part of `evaluate_formulae()`. This involves: 1. Parsing the formula ([source](https://github.com/pythonanywhere/dirigible-spreadsheet/blob/master/dirigible/sheet/formula_interpreter.py)) - cell formulae can include any valid python, as well as - special spreadsheet syntax, including cell references like A1 or B2, and special spreadsheet formulae like the SUM function - any special spreadsheet syntax is parsed and converted to Python - and finding out the `dependencies`. If the formula for cell A1 includes a reference to B1, then B1 is a dependency of A1 2. Placing it into the whole spreadsheet's dependency graph ([source](https://github.com/pythonanywhere/dirigible-spreadsheet/blob/master/dirigible/sheet/dependency_graph.py)) 3. Evaluating all the branches of that graph, starting from its leaves, by evaluating the cell's formula to get its value. That can then be fed into cells that depend on it, and so on. (See [calculate.py](https://github.com/pythonanywhere/dirigible-spreadsheet/blob/master/dirigible/sheet/calculate.py)) Cell formulae can also use any user-defined functions from the usercode. Everything else is fairly peripheral. There is some monkeying around with json, some stuff with threads that's a throwback to some parallelisation features that I haven't finished removing. Questions and requests for clarification are solicited, as are any pull requests for documentation, bugfixes, fixes to unit tests (of which all but three were passing in this modified version of our codebase, at the time of writing), or FTs (half-ported from old selenium) ================================================ FILE: dirigible/.gitignore ================================================ db.sqlite3 fts/screendumps ================================================ FILE: dirigible/dirigible/__init__.py ================================================ ================================================ FILE: dirigible/dirigible/settings.py ================================================ """ Django settings for dirigible project. For more information on this file, see https://docs.djangoproject.com/en/dev/topics/settings/ For the full list of settings and their values, see https://docs.djangoproject.com/en/dev/ref/settings/ """ # Build paths inside the project like this: os.path.join(BASE_DIR, ...) import os BASE_DIR = os.path.dirname(os.path.dirname(__file__)) # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/dev/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = '0590fte(p#@fk+ii_-vfzwuixst9z)nqzszr!wx7pkjv++sa%2' # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True TEMPLATE_DEBUG = True ALLOWED_HOSTS = [] # Application definition INSTALLED_APPS = ( 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'registration', 'user', 'feedback', 'info_pages', 'featured_sheet', 'sheet', 'shared', ) MIDDLEWARE_CLASSES = ( 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ) ROOT_URLCONF = 'dirigible.urls' WSGI_APPLICATION = 'dirigible.wsgi.application' # Database # https://docs.djangoproject.com/en/dev/ref/settings/#databases DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), } } # Internationalization # https://docs.djangoproject.com/en/dev/topics/i18n/ LANGUAGE_CODE = 'en-us' TIME_ZONE = 'UTC' USE_I18N = True USE_L10N = True USE_TZ = False # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/dev/howto/static-files/ STATIC_URL = '/static/' LOGIN_URL = '/login' LOGIN_REDIRECT_URL = '/' FEEDBACK_EMAIL = 'support@example.com' ================================================ FILE: dirigible/dirigible/test_utils.py ================================================ # Copyright (c) 2005-2010 Resolver Systems Ltd, PythonAnywhere LLP # See LICENSE.md # try: import unittest2 as unittest except ImportError: import unittest from os.path import dirname, join import sys import django def create_suite_for_file_directory(file): def suite(): start_dir = dirname(file) return unittest.defaultTestLoader.discover( start_dir, top_level_dir=join(dirname(__file__), "..") ) return suite def die(exception=None): if exception is None: exception = AssertionError('die called') def inner_die(*_): raise exception return inner_die class ResolverTestMixins(object): def assertCalledOnce(self, mock, *args, **kwargs): if mock.call_args_list == []: self.fail('Not called') self.assertEquals(mock.call_args_list, [(args, kwargs)]) class ResolverTestCase(unittest.TestCase, ResolverTestMixins): maxDiff = None class ResolverDjangoTestCase(django.test.TestCase, ResolverTestMixins): maxDiff = None TEST_CLASSES = ( unittest.TestCase, django.test.TestCase, django.test.TransactionTestCase, ResolverTestCase, ResolverDjangoTestCase ) def assert_security_classes_exist(test, module_name, excludes=None): ''' ensure that, as a minimum sanity check, each non-security test class in this module has an associated security test class. ''' test_classes = [ name for name, item in sys.modules[module_name].__dict__.iteritems() if isinstance(item, type) and issubclass(item, TEST_CLASSES) and not item in TEST_CLASSES ] regular_test_classes = [ name for name in test_classes if not name.endswith('SecurityTest') ] if excludes is None: excludes = [] for name in regular_test_classes: if name not in excludes: test.assertTrue( name[:-4] + 'SecurityTest' in test_classes, "class %s doesn't have a security test. " "Use user page security test as template" % (name,) ) ================================================ FILE: dirigible/dirigible/urls.py ================================================ import os from django.conf.urls import patterns, include, url from django.contrib import admin from django.conf import settings from django.views.generic import TemplateView from info_pages.views import front_page_view, info_page_view from sheet.views import new_sheet urlpatterns = patterns( '', url( r'^$', front_page_view, name='front_page' ), url( r'^(?Poss|video)/', info_page_view, name="info_page" ), url( # If you change this, don't forget to change the LOGIN_URL in settings.py # Here be dragons. The settings .py one has no trailing slash and needs to # stay that way. Changing either of these will stop it from working in Chrome # and in Firefox if you press ENTER to login. r'^login/', 'django.contrib.auth.views.login', {'template_name': 'login.html'}, name="login" ), url( r'^logout$', 'django.contrib.auth.views.logout', {'next_page': settings.LOGIN_URL}, name="logout" ), url( r'^new_sheet$', new_sheet, name="new_sheet" ), url( r'^user/', include('user.urls') ), url( r'^signup/', include('user.signup_urls') ), url( r'^feedback/', include('feedback.urls') ), url( r'^featured_sheets/$', TemplateView.as_view(template_name='featured_sheets.html'),#, context={'sheets': FeaturedSheet.objects.all}), name='featured_sheets' ), url(r'^admin/', include(admin.site.urls)), ) ================================================ FILE: dirigible/dirigible/wsgi.py ================================================ """ WSGI config for dirigible project. It exposes the WSGI callable as a module-level variable named ``application``. For more information on this file, see https://docs.djangoproject.com/en/dev/howto/deployment/wsgi/ """ import os os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dirigible.settings") from django.core.wsgi import get_wsgi_application application = get_wsgi_application() ================================================ FILE: dirigible/featured_sheet/__init__.py ================================================ ================================================ FILE: dirigible/featured_sheet/admin.py ================================================ # Copyright (c) 2011 Resolver Systems Ltd, PythonAnywhere LLP # See LICENSE.md # from featured_sheet.models import FeaturedSheet from django.contrib import admin admin.site.register(FeaturedSheet) ================================================ FILE: dirigible/featured_sheet/models.py ================================================ # Copyright (c) 2011 Resolver Systems Ltd, PythonAnywhere LLP # See LICENSE.md # from django.db import models from sheet.models import Sheet class FeaturedSheet(models.Model): sheet = models.ForeignKey(Sheet) description = models.TextField() more_info_url = models.CharField(max_length=1024, default='', blank=True) def __unicode__(self): return 'Feature: %s' % (self.sheet.name,) ================================================ FILE: dirigible/featured_sheet/templates/featured_sheets.html ================================================ {% extends "info_page.html" %} {% block title %} Featured Sheets: Dirigible {% endblock %} {% block head %} {{ block.super }} {% endblock %} {% block middle %}

Featured sheets

{% for sheet in sheets %}
{% endfor %} {% endblock %} ================================================ FILE: dirigible/featured_sheet/tests/__init__.py ================================================ ================================================ FILE: dirigible/featured_sheet/tests/test_models.py ================================================ # Copyright (c) 2011 Resolver Systems Ltd, PythonAnywhere LLP # See LICENSE.md # from django.contrib.auth.models import User from dirigible.test_utils import ResolverTestCase from sheet.models import Sheet from featured_sheet.models import FeaturedSheet class TestFeaturedSheetModel(ResolverTestCase): def test_can_construct_without_more_info_url(self): user = User(username='featurer') user.save() sheet = Sheet(owner=user, name='sheet to feature') sheet.save() description = 'twas brillig and the slithy toves' fs = FeaturedSheet(sheet=sheet, description=description) fs.save() self.assertEquals(fs.sheet, sheet) self.assertEquals(fs.description, description) self.assertEquals(fs.more_info_url, '') def test_can_construct_with_more_info_url(self): user = User(username='chattyfeaturer') user.save() sheet = Sheet(owner=user, name='sheet to feature') sheet.save() description = 'twas brillig and the slithy toves' more_info_url = 'http://far.away/' fs = FeaturedSheet(sheet=sheet, description=description, more_info_url=more_info_url) fs.save() self.assertEquals(fs.sheet, sheet) self.assertEquals(fs.description, description) self.assertEquals(fs.more_info_url, more_info_url) def test_unicode(self): user = User(username='printyfeaturer') user.save() sheet = Sheet(owner=user, name='sheet to feature') sheet.save() description = 'twas brillig and the slithy toves' more_info_url = 'http://far.away/' fs = FeaturedSheet(sheet=sheet, description=description, more_info_url=more_info_url) fs.save() self.assertEquals(unicode(fs), u'Feature: %s' % (sheet.name,)) ================================================ FILE: dirigible/featured_sheet/views.py ================================================ # Copyright (c) 2011 Resolver Systems Ltd, PythonAnywhere LLP # See LICENSE.md # ================================================ FILE: dirigible/feedback/__init__.py ================================================ ================================================ FILE: dirigible/feedback/models.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd, PythonAnywhere LLP # See LICENSE.md # from django.db import models ================================================ FILE: dirigible/feedback/tests/__init__.py ================================================ ================================================ FILE: dirigible/feedback/tests/test_views.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd, PythonAnywhere LLP # See LICENSE.md # from mock import patch from textwrap import dedent from django.conf import settings from django.http import HttpRequest, HttpResponse from dirigible.test_utils import ResolverTestCase from feedback.views import submit class SubmitTest(ResolverTestCase): @patch('feedback.views.send_mail') def test_submit_with_message_and_email_address_and_username_sends_admin_email_with_all_three(self, mock_send_mail): request = HttpRequest() request.POST["message"] = "a test message" request.POST["email_address"] = "a test email address" request.POST["username"] = "a test username" request.META['HTTP_REFERER'] = 'a test page' response = submit(request) self.assertTrue(isinstance(response, HttpResponse)) self.assertEquals(response.content, "OK") self.assertCalledOnce( mock_send_mail, "User feedback from Dirigible", dedent(""" Username: a test username Email address: a test email address Page: a test page Message: a test message """), settings.SERVER_EMAIL, [settings.FEEDBACK_EMAIL] ) ================================================ FILE: dirigible/feedback/urls.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd, PythonAnywhere LLP # See LICENSE.md # from django.conf.urls import * from feedback.views import submit urlpatterns = patterns('', url( r'^submit/$', submit, name="feedback_submit" ), ) ================================================ FILE: dirigible/feedback/views.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd, PythonAnywhere LLP # See LICENSE.md # from textwrap import dedent from django.core.mail import send_mail from django.http import HttpResponse from django.conf import settings def submit(request): send_mail( "User feedback from Dirigible", dedent(""" Username: %s Email address: %s Page: %s Message: %s """) % ( request.POST["username"], request.POST["email_address"], request.META['HTTP_REFERER'], request.POST["message"] ), settings.SERVER_EMAIL, [settings.FEEDBACK_EMAIL] ) return HttpResponse("OK") ================================================ FILE: dirigible/fts/__init__.py ================================================ ================================================ FILE: dirigible/fts/screendumps/placeholder ================================================ ================================================ FILE: dirigible/fts/tests/__init__.py ================================================ ================================================ FILE: dirigible/fts/tests/functionaltest.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from __future__ import print_function from contextlib import contextmanager from email.parser import Parser from functools import wraps from textwrap import dedent from threading import Thread from urlparse import urljoin, urlparse, urlunparse import datetime import hashlib import os import re import time import urllib import urllib2 from django.conf import settings from django.contrib.auth import BACKEND_SESSION_KEY, SESSION_KEY, HASH_SESSION_KEY from django.contrib.auth.models import User from django.contrib.sessions.backends.db import SessionStore from django.contrib.staticfiles.testing import StaticLiveServerTestCase from selenium import webdriver from selenium.common.exceptions import NoSuchElementException from selenium.webdriver.common.action_chains import ActionChains from selenium.webdriver.common.keys import Keys from sheet.sheet import Sheet USER_PASSWORD = 'p4ssw0rd' DEFAULT_WAIT_FOR_TIMEOUT = 2 DEFAULT_TYPING_WAIT = 0.1 CURRENT_API_VERSION = '0.1' SCREEN_DUMP_LOCATION = os.path.abspath( os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'screendumps') ) IMAP_HOST = "" IMAP_USERNAME = "" IMAP_PASSWORD = "" def _debug(text): msg = '{} {}'.format(round(time.time(), 2), text) print(msg) # print(msg, file=sys.stderr) class Url(object): ROOT = 'http://localhost:8081/' LOGIN = urljoin(ROOT, '/login/') LOGOUT = urljoin(ROOT, '/logout') NEW_SHEET = urljoin(ROOT, '/new_sheet') SIGNUP = urljoin(ROOT, '/signup/register/') DOCUMENTATION = urljoin(ROOT, '/documentation/') API_DOCS = urljoin(DOCUMENTATION, 'builtins.html') @classmethod def user_page(cls, username): return urljoin(Url.ROOT, '/user/%s/' % (username,)) @classmethod def sheet_page(cls, username, sheet_id): return urljoin(cls.user_page(username), 'sheet/%s/' % (sheet_id,)) @classmethod def api_url(cls, username, sheet_id): return urljoin(cls.sheet_page(username, sheet_id), 'v%s/json/' % (CURRENT_API_VERSION,)) def snapshot_on_error(test): @wraps(test) def inner(*args, **kwargs): try: test(*args, **kwargs) except: test_object = args[0] try: filename = test_object.get_dump_filename() _debug('screenshot to {}.png'.format(filename)) test_object.browser.get_screenshot_as_file(filename + '.png') _debug('page source dump to {}.html'.format(filename)) with open(filename + '.html', 'w') as f: f.write(test_object.browser.page_source.encode('utf8')) _debug('page text dump to {}.txt'.format(filename)) with open(filename + '.txt', 'w') as f: body_text = test_object.browser.find_element_by_tag_name('body').text f.write(body_text.encode('utf8')) except: _debug('Exception writing screenshots') raise return inner def humanesque_delay(length=DEFAULT_TYPING_WAIT): time.sleep(length) def humanise_with_delay(action): @wraps(action) def inner(*args, **kwargs): humanesque_delay() result = action(*args, **kwargs) humanesque_delay() return result return inner class Bounds(object): def __init__(self, width, height, top, left): self.width = width self.height = height self.top = top self.left = left bottom = property(lambda self: self.top + self.height) right = property(lambda self: self.left + self.width) RGB_RE = re.compile('^rgba?\((\d+), (\d+), (\d+)(, (\d+))?\)') def convert_rgb_to_hex(value): match = RGB_RE.match(value) r, g, b = match.group(1), match.group(2), match.group(3) return '#%X%X%X' % (int(r), int(g), int(b)) class FunctionalTest(StaticLiveServerTestCase): user_count = 1 def wait_for( self, condition_function, msg_function, timeout_seconds=DEFAULT_WAIT_FOR_TIMEOUT, allow_exceptions=False ): start = time.time() end = start + timeout_seconds exception_raised = False tries = 0 while tries < 2 or time.time() < end: _debug('Waiting for {}'.format(msg_function()[:30])) try: tries += 1 if condition_function(): return exception_raised = False except Exception, e: if not allow_exceptions: raise e exception_raised = True time.sleep(0.1) if exception_raised: raise self.fail("Timeout waiting for condition: %s" % (msg_function(),)) def get_dump_filename(self): timestamp = datetime.datetime.now().isoformat().replace(':', '.')[:19] return '{folder}/{test_id}-{timestamp}'.format( folder=SCREEN_DUMP_LOCATION, test_id=self.id(), timestamp=timestamp ) session_keys = {} def create_users(self): for username in self.get_my_usernames(): user = User.objects.create(username=username) user.set_password('p4ssw0rd') user.save() profile = user.get_profile() profile.has_seen_sheet_page = True profile.save() # create sessions we can use for login too session = SessionStore() session[SESSION_KEY] = user.pk session[BACKEND_SESSION_KEY] = settings.AUTHENTICATION_BACKENDS[0] session[HASH_SESSION_KEY] = user.get_session_auth_hash() session.save() self.session_keys[username] = session.session_key def setUp(self): self.create_users() _debug("%s ##### Running test %s" % (datetime.datetime.now(), self.id())) self.browser = webdriver.Firefox() self.browser.implicitly_wait(DEFAULT_WAIT_FOR_TIMEOUT) self.browser.set_window_size(1024, 768) def tearDown(self): _debug('quitting browser') self.browser.quit() _debug("%s ##### Finished test %s" % (datetime.datetime.now(), self.id())) def login( self, username=None, password=USER_PASSWORD, manually=False ): if username is None: username = self.get_my_username() if manually: self.get_element('id=id_username').clear() self.get_element('id=id_password').clear() self.get_element('id=id_username').send_keys(username) self.get_element('id=id_password').send_keys(password) self.click_link('id_login') return session_key = self.session_keys[username] ## to set a cookie we need to first visit the domain. ## 404 pages load the quickest! self.browser.get(urljoin(Url.ROOT, "/404_no_such_url/")) self.browser.add_cookie(dict( name=settings.SESSION_COOKIE_NAME, value=session_key, path='/', )) self.go_to_url(Url.ROOT) def logout(self): self.go_to_url(Url.LOGOUT) def get_element(self, locator): if locator.startswith('css='): return self.browser.find_element_by_css_selector(locator[4:]) elif locator.startswith('id='): return self.browser.find_element_by_id(locator[3:]) def is_element_focused(self, locator): element = self.get_element(locator) focused_element = self.browser.switch_to_active_element() return element == focused_element def is_element_present(self, locator): try: self.get_element(locator) return True except NoSuchElementException: return False def get_text(self, locator): return self.get_element(locator).text def get_value(self, locator): return self.get_element(locator).get_attribute('value') def human_key_press(self, key_code): _debug('pressing key %r' % (key_code,)) self.browser.switch_to_active_element().send_keys(key_code) @contextmanager def key_down(self, key_code): _debug('key down %r' % (key_code,)) self.browser.switch_to_active_element().send_keys(key_code) ActionChains(self.browser).key_down(key_code).perform() yield # apparently there's no need for a key up?? # ActionChains(self.browser).key_up(key_code).perform() def click_to_and_blur_from(self, click_to_locator, blur_from_locator): self.selenium.fire_event(blur_from_locator, 'blur') self.selenium.click(click_to_locator) def get_element_bounds(self, locator): return Bounds( self.selenium.get_element_width(locator), self.selenium.get_element_height(locator), self.selenium.get_element_position_top(locator), self.selenium.get_element_position_left(locator) ) def get_css_property(self, jquery_locator, property_name): property_value = self.selenium.get_eval('window.$("%s").css("%s")' % (jquery_locator, property_name)) if property_value == 'rgba(0, 0, 0, 0)': # transparent in chrome return 'transparent' if property_value.startswith('rgb'): property_value = convert_rgb_to_hex(property_value) if property_value.startswith('#'): property_value = property_value.upper() if len(property_value) == 4: _, r, g, b = property_value property_value = '#%s%s%s%s%s%s' % (r, r, g, g, b, b) return property_value def assert_urls_are_same(self, actual, expected): loc = self.browser.current_url canonicalised_actual = urljoin(loc, actual) canonicalised_expected = urljoin(loc, expected) self.assertEquals(canonicalised_actual, canonicalised_expected) def assert_HTTP_error(self, url, error_code): self.browser.get(url) possible_error_locators = ('id=summary', 'id=id_server_error_title') for error_locator in possible_error_locators: if self.is_element_present(error_locator) and str(error_code) in self.get_text(error_locator): return self.fail('%d not raised, got: %s' % (error_code, self.browser.title)) def assert_redirects(self, from_url, to_url): self.go_to_url(from_url) self.assert_urls_are_same( urlunparse(urlparse(self.browser.current_url)[:4] + ('', '')), to_url ) def is_element_enabled(self, element_id): #self.selenium.get_attribute is unreliable (Harry, Jonathan) disabled = self.selenium.get_eval('window.$("#%s").attr("disabled")' % (element_id,)) return disabled not in ("true", "disabled") def wait_for_element_presence( self, locator, present=True, timeout_seconds=DEFAULT_WAIT_FOR_TIMEOUT ): if present: failure_message = "Element %s to be present" % (locator, ), else: failure_message = "Element %s to not exist" % (locator, ), self.wait_for( lambda: self.is_element_present(locator) == present, lambda: failure_message, timeout_seconds=timeout_seconds ) def wait_for_element_to_appear(self, locator, timeout_seconds=DEFAULT_WAIT_FOR_TIMEOUT): self.wait_for_element_presence(locator, True, timeout_seconds) def wait_for_element_text(self, locator, text, timeout_seconds=DEFAULT_WAIT_FOR_TIMEOUT): self.wait_for( lambda: self.get_text(locator) == text, lambda: "Element %s to contain text %r. Contained %r" % (locator, text, self.get_text(locator)), timeout_seconds=timeout_seconds ) def wait_for_element_visibility(self, locator, visibility, timeout_seconds=DEFAULT_WAIT_FOR_TIMEOUT): self.wait_for( lambda : self.selenium.is_visible(locator) == visibility, lambda : "Element %s to become%svisible" % (locator, visibility and ' ' or ' in'), timeout_seconds=timeout_seconds ) def get_url_with_session_cookie(self, url, data=None): opener = urllib2.build_opener() session_cookie = self.selenium.get_cookie_by_name('sessionid') opener.addheaders.append(('Cookie', 'sessionid=%s' % (session_cookie, ))) if data is None: return opener.open(url) else: encoded_data = urllib.urlencode(data) return opener.open(url, encoded_data) def create_new_sheet(self, username=None, manually=False): if username is None: username = self.get_my_username() user = User.objects.get(username=username) sheet = Sheet(owner=user) sheet.save() self.browser.get(Url.sheet_page(username, sheet.id)) return sheet.id def login_and_create_new_sheet(self, username=None): self.login(username=username) return self.create_new_sheet(username=username) def get_my_usernames(self): usernames = [] for user_index in range(self.user_count): capture_test_details = re.compile(r'test_(\d+)_[^\.]*\.[^\.]*\.test_(.*)$') match = re.search(capture_test_details, self.id()) test_task_id = match.group(1) test_method_name = match.group(2) test_method_hash = hashlib.md5(test_method_name).hexdigest()[:7] usernames.append(("tstusr_%s_%s" % (test_task_id, test_method_hash))[:29] + str(user_index)) return usernames def get_my_username(self): return self.get_my_usernames()[0] def _check_page_link_home(self): if self.browser.current_url.startswith(Url.DOCUMENTATION): return link = None for possible_id in ('id_small_header_logo', 'id_big_logo'): try: link = self.browser.find_element_by_xpath( "//a[img[@id='{img_id}']]".format(img_id=possible_id) ) self.assertEqual(link.get_attribute('href'), Url.ROOT) return except NoSuchElementException: pass self.fail("Could not find a logo that is also a link on page {}".format( self.browser.current_url )) def check_page_load(self, link_destination=None): self._check_page_link_home() def go_to_url(self, url): _debug('going to url ' + url) self.browser.get(url) self.check_page_load(url) def refresh_sheet_page(self): self.browser.refresh() self.wait_for_grid_to_appear() def click_link(self, element_id): link = self.browser.find_element_by_id(element_id) link.click() def set_sheet_name(self, name): self.selenium.click('id=id_sheet_name') self.wait_for( lambda: self.is_element_present('id=edit-id_sheet_name'), lambda: 'editable sheetname to appear') self.selenium.type('id=edit-id_sheet_name', name) self.human_key_press('\n') self.wait_for( lambda: self.get_text('id=id_sheet_name') == name, lambda: 'sheet name to be updated' ) def assert_sends_to_login_page(self, requested_url): self.assert_redirects(requested_url, Url.LOGIN) def assert_sends_to_root_page(self, requested_url): self.assert_redirects(requested_url, Url.ROOT) def assert_page_title_contains(self, link_url, title): original_page = self.browser.current_url self.go_to_url(link_url) self.assertTrue(title in self.browser.title) self.go_to_url(original_page) def assert_has_useful_information_links(self): self.browser.find_elements_by_link_text('Terms & Conditions') self.browser.find_elements_by_link_text('Privacy Policy') self.browser.find_elements_by_link_text('Contact Us') def get_cell_css(self, column, row, must_be_active=False): active_classes = '' if must_be_active: active_classes = '.active' return 'div.slick-row[row="%d"] div.slick-cell.c%d%s' % ( row - 1, column, active_classes ) def get_cell_locator(self, column, row, must_be_active=False): return 'css=%s' % (self.get_cell_css(column, row, must_be_active),) def get_cell_formatted_value_locator(self, column, row, raise_if_cell_missing=True): cell_css = self.get_cell_css(column, row) if not self.is_element_present('css=%s' % (cell_css,)): if raise_if_cell_missing: raise Exception("Cell not present at %s, %s" % (column, row)) else: return None return 'css=%s span.grid_formatted_value' % (cell_css,) cell_editor_css = 'input.editor-text' def get_active_cell_editor_locator(self): return 'css={}'.format(self.cell_editor_css) def get_cell_editor_locator(self, column, row): cell_css = self.get_cell_css(column, row) return 'css=%s %s' % (cell_css, self.cell_editor_css) def get_cell_editor(self): return self.get_element(self.get_active_cell_editor_locator()) def is_cell_visible(self, column, row): tries = 0 while tries < 4: try: return 'true' == self.selenium.get_eval(dedent( ''' (function () { var viewport = window.grid.getViewport(); if (viewport.top > %(row)s || %(row)s > viewport.bottom) { return false; } var $canvasDiv = window.$('div.grid-canvas'); var $viewportDiv = window.$('div.slick-viewport'); var viewableLeft = -$canvasDiv.position().left; var viewableRight = viewableLeft + $viewportDiv.width(); var $currentCell = window.$('%(current_cell_css)s'); var currentCellLeft = $currentCell.position().left; var currentCellRight = currentCellLeft + $currentCell.outerWidth(); if (viewableLeft > currentCellLeft || currentCellRight > viewableRight) { return false; } return true; })() ''' % dict(row=row, col=column, current_cell_css=self.get_cell_css(column, row) ) ) ) except: time.sleep(1) tries += 1 self.fail("Could not check for cell visibility at %s, %s after %s tries" % (column, row, tries)) def assert_cell_visible(self, column, row): self.assertTrue( self.is_cell_visible(column, row), 'cell %s, %s not visible' % (column, row) ) def wait_for_cell_to_be_visible( self, column, row, timeout_seconds=DEFAULT_WAIT_FOR_TIMEOUT ): self.wait_for( lambda: self.is_cell_visible(column, row), lambda: "Cell at %s, %s to become visible" % (column, row), allow_exceptions=True, timeout_seconds=timeout_seconds ) def get_formula_bar_id(self): return "id_formula_bar" def get_formula_bar_locator(self): return "id=%s" % (self.get_formula_bar_id(),) def is_formula_bar_enabled(self): return self.is_element_enabled(self.get_formula_bar_id()) def scroll_cell_row_into_view(self, column, row): self.browser.execute_script( 'window.grid.scrollRowIntoView({row}, true);'.format(row=row - 1) ) self.wait_for_element_to_appear(self.get_cell_locator(column, row)) def go_to_cell(self, column, row): self.selenium.get_eval( 'window.grid.gotoCell(%s, %s, false)' % (row - 1, column)) self.wait_for_element_to_appear(self.get_cell_locator(column, row)) @humanise_with_delay def click_on_cell(self, column, row): self.scroll_cell_row_into_view(column, row) self.get_element(self.get_cell_locator(column, row)).click() def select_range_with_shift_click(self, start, end): self.click_on_cell(*start) with self.key_down(key_codes.SHIFT): self.click_on_cell(*end) self.assert_current_selection(start, end) def mouse_drag(self, cell_from, cell_to): from_locator = self.get_cell_locator(*cell_from) to_locator = self.get_cell_locator(*cell_to) pixel_offset = "10,30" #pixel offset fixes selenium weird tendency to click too far north-west. #may cause problems if column widths are reduced... self.selenium.mouse_down_at(from_locator, pixel_offset) humanesque_delay(1) self.selenium.mouse_move_at(from_locator, pixel_offset) humanesque_delay(1) self.selenium.mouse_move_at(to_locator, pixel_offset) humanesque_delay(1) self.selenium.mouse_up_at(to_locator, pixel_offset) humanesque_delay(1) def assert_current_selection(self, topleft, bottomright, thoroughly=True): if thoroughly: for row in range(topleft[1],bottomright[1] + 1): for col in range(topleft[0],bottomright[0] + 1): locator = self.get_cell_locator(col, row) + '.selected' self.wait_for_element_to_appear(locator) else: topleft_locator = self.get_cell_locator(*topleft) + '.selected' bottomright_locator = ( self.get_cell_locator(*bottomright) + '.selected' ) self.wait_for_element_to_appear(topleft_locator) self.wait_for_element_to_appear(bottomright_locator) def open_cell_for_editing(self, column, row): self.scroll_cell_row_into_view(column, row) ActionChains(self.browser).double_click( self.get_element(self.get_cell_locator(column, row)) ).perform() self.wait_for_cell_to_enter_edit_mode(column, row) def type_into_cell_editor_unhumanized(self, text): self.get_cell_editor().send_keys(text) @humanise_with_delay def enter_cell_text(self, col, row, text): self.enter_cell_text_unhumanized(col, row, text) def enter_cell_text_unhumanized(self, col, row, text): self.open_cell_for_editing(col, row) self.type_into_cell_editor_unhumanized(text) self.type_into_cell_editor_unhumanized('\n') # self.wait_for_cell_to_contain_formula(text) def get_current_cell(self): row = int(self.browser.execute_script( 'return window.grid.getActiveCell().row;') ) + 1 column = int(self.browser.execute_script( 'return window.grid.getActiveCell().cell;' )) return column, row def get_cell_text(self, column, row): self.scroll_cell_row_into_view(column, row) text = self.get_text(self.get_cell_locator(column, row)) return text def get_cell_editor_content(self): return self.get_cell_editor().get_attribute('value') def get_cell_shown_formula_locator(self, column, row, raise_if_cell_missing=True): cell_css = self.get_cell_css(column, row) if not self.is_element_present('css=%s' % (cell_css,)): if raise_if_cell_missing: raise Exception("Cell not present at %s, %s" % (column, row)) else: return None return 'css=%s span.grid_formula' % (cell_css,) def get_cell_shown_formula(self, column, row, raise_if_cell_missing=True): formula_locator = self.get_cell_shown_formula_locator( column, row, raise_if_cell_missing ) if not self.is_element_present(formula_locator): return None formula = self.get_text(formula_locator) return formula def assert_cell_shown_formula(self, column, row, formula): self.assertEquals(self.get_cell_shown_formula(column, row), formula) def wait_for_cell_shown_formula(self, column, row, formula, timeout_seconds=DEFAULT_WAIT_FOR_TIMEOUT): def generate_failure_message(): return ( "cell %d, %d to show formula '%s', was %r -- text is %r" % ( column, row, formula, self.get_cell_shown_formula(column, row), self.get_cell_text(column, row)) ) self.wait_for( lambda : self.get_cell_shown_formula(column, row, raise_if_cell_missing=False) == formula, generate_failure_message, allow_exceptions=True, timeout_seconds=timeout_seconds ) def wait_for_cell_to_contain_formula(self, column, row, formula): self.open_cell_for_editing(column, row) self.wait_for_cell_editor_content(formula) self.get_cell_editor().send_keys('\n') error_img_locator = 'id=id_{col}_{row}_error' def get_cell_error(self, column, row): if self.is_element_present(self.error_img_locator.format(col=column, row=row)): return self.get_element(self.error_img_locator.format(col=column, row=row)).get_attribute('title') def assert_cell_has_error(self, column, row, error_text): self.wait_for_element_to_appear(self.error_img_locator.format(col=column, row=row)) self.assertEquals(self.get_cell_error(column, row), error_text) def assert_cell_has_no_error(self, column, row): self.assertFalse( self.is_element_present(self.error_img_locator.format(col=column, row=row)), 'Error present for (%d, %d)' % (column, row) ) def assert_cell_is_current_but_not_editing(self, col, row): self.wait_for_cell_to_become_active(col, row) self.assertFalse( self.is_element_focused(self.get_cell_editor_locator(col, row)) ) def assert_cell_is_current_and_is_editing(self, col, row): self.wait_for_cell_to_become_active(col, row) self.assertTrue( self.is_element_focused(self.get_cell_editor_locator(col, row)) ) def wait_for_cell_value( self, column, row, value_or_regex, timeout_seconds=DEFAULT_WAIT_FOR_TIMEOUT ): _debug('waiting for cell {},{} value {}'.format( column, row, value_or_regex, )) def match(text): if hasattr(value_or_regex, 'match'): return value_or_regex.match(text) else: return text == value_or_regex def cell_shows_value(): self.last_found_value = self.get_cell_text(column, row) return ( match(self.last_found_value) and self.get_cell_shown_formula( column, row, raise_if_cell_missing=False ) is None ) def generate_failure_message(): actual_value = self.last_found_value self.last_found_value = None actual_formula = '' if self.get_cell_shown_formula(column, row) is not None: actual_formula = self.last_found_value actual_value = '' actual_error = self.get_cell_error(column, row) return ( "Cell at (%s, %s) to become %r " "(value=%r, shown formula=%r, error=%r)" % ( column, row, value_or_regex, actual_value, actual_formula, actual_error) ) self.last_found_value = None try: self.wait_for( cell_shows_value, generate_failure_message, timeout_seconds=timeout_seconds, allow_exceptions=True ) finally: _debug('finished waiting for cell value') def wait_for_cell_to_become_active( self, column, row, timeout_seconds=DEFAULT_WAIT_FOR_TIMEOUT ): locator = self.get_cell_locator(column, row, must_be_active=True) self.wait_for( lambda: self.is_element_present(locator), lambda: "Cell at (%s, %s) was not active. Selection is: %s" % ( column, row, self.get_current_cell() ), timeout_seconds=timeout_seconds ) def wait_for_cell_to_enter_edit_mode( self, column, row, timeout_seconds=DEFAULT_WAIT_FOR_TIMEOUT ): self.wait_for_cell_to_become_active(column, row) full_editor_locator = self.get_cell_editor_locator(column, row) self.wait_for( lambda: self.is_element_focused(full_editor_locator), lambda: "Editor at (%s, %s) to get focus" % (column, row), timeout_seconds=timeout_seconds ) def wait_for_cell_editor_content(self, content): self.wait_for( lambda: self.get_cell_editor_content() == content, lambda: "Cell editor to become %s (was '%s')" % ( content, self.get_cell_editor_content() ), ) def get_viewport_top(self): return int(self.selenium.get_eval( 'window.grid.getViewport().top' ) ) + 1 def get_viewport_bottom(self): return int(self.selenium.get_eval( 'window.grid.getViewport().bottom' ) ) + 1 def is_spinner_visible(self): return ( self.is_element_present('css=#id_spinner_image') and not self.is_element_present('css=#id_spinner_image.hidden') ) def wait_for_spinner_to_stop(self, timeout_seconds=DEFAULT_WAIT_FOR_TIMEOUT): self.wait_for( lambda : not self.is_spinner_visible(), lambda : "Spinner to disappear", timeout_seconds=timeout_seconds ) def wait_for_grid_to_appear(self, timeout_seconds=DEFAULT_WAIT_FOR_TIMEOUT): self.wait_for_element_to_appear(self.get_cell_locator(1, 1), timeout_seconds) def get_usercode(self): return self.browser.execute_script( 'return window.editor.session.getValue();' ).replace('\r\n', '\n') @humanise_with_delay def enter_usercode(self, code, commit_change=True): self.browser.execute_script( "window.editor.session.setValue(%s);" % ( repr(unicode(code))[1:], ) ) if commit_change: self.human_key_press(Keys.F9) def append_usercode(self, code): self.enter_usercode("%s\n%s" % (self.get_usercode(), code)) def prepend_usercode(self, code): self.enter_usercode("%s\n%s" % (code, self.get_usercode())) def wait_for_usercode_editor_content( self, content, timeout_seconds=DEFAULT_WAIT_FOR_TIMEOUT ): self.wait_for( lambda: self.get_usercode().strip() == content.strip(), lambda: ( 'Usercode editor content to become \n' + content + '\n' + '-=' * 10 + '\nwas:\n' + self.get_usercode() ), timeout_seconds=timeout_seconds ) def sanitise_console_content(self, content): # IE has char 13 for return instead of the normal Unix 10. # Not sure why it differs from Chrome and Firefox. return content.replace('\r', '\n') def get_console_content(self): content = self.selenium.get_eval('window.$("#id_console").text()') return self.sanitise_console_content(content) def wait_for_console_content(self, content, timeout_seconds=DEFAULT_WAIT_FOR_TIMEOUT): self.wait_for( lambda: content in self.get_console_content(), lambda : 'error console to contain "%s" (was "%s")' % (content, self.get_console_content()), timeout_seconds=timeout_seconds ) def get_formula_bar_contents(self): return self.selenium.get_value(self.get_formula_bar_locator()) def assert_formula_bar_contains(self, contents): self.assertEquals(self.get_formula_bar_contents(), contents) def wait_for_formula_bar_contents(self, contents, timeout_seconds=DEFAULT_WAIT_FOR_TIMEOUT): self.wait_for( lambda : self.get_formula_bar_contents() == contents, lambda : 'formula bar to contain "%s" (was "%s")' % (contents, self.get_formula_bar_contents() ), timeout_seconds=timeout_seconds ) def click_formula_bar(self): self.selenium.click(self.get_formula_bar_locator()) self.wait_for( lambda : self.is_element_focused(self.get_formula_bar_locator()), lambda : "Formula bar to gain focus" ) def copy_range(self, start, end): self.click_on_cell(*start) with self.key_down(key_codes.SHIFT): self.click_on_cell(*end) self.assert_current_selection(start, end) with self.key_down(key_codes.CTRL): self.selenium.key_press_native(key_codes.LETTER_C) def cut_range(self, start, end): self.click_on_cell(*start) with self.key_down(key_codes.SHIFT): self.click_on_cell(*end) self.assert_current_selection(start, end) with self.key_down(key_codes.CTRL): self.selenium.key_press_native(key_codes.LETTER_X) self.wait_for_spinner_to_stop() def paste_range(self, start, end=None): self.click_on_cell(*start) if end: with self.key_down(key_codes.SHIFT): self.click_on_cell(*end) with self.key_down(key_codes.CTRL): self.selenium.key_press_native(key_codes.LETTER_V) self.wait_for_spinner_to_stop() def set_filename_for_upload(self, file_name, field_selector): if self.selenium.browserStartCommand == '*firefox': self.selenium.focus(field_selector) self.selenium.type(field_selector, file_name) self.selenium.click(field_selector) else: def handle_file_dialog(): time.sleep(2) SendKeys.SendKeys('{ENTER}') time.sleep(2) escaped_filename = file_name.replace('~','{~}') SendKeys.SendKeys(escaped_filename) SendKeys.SendKeys('{ENTER}') time.sleep(2) dialog_thread = Thread(target=handle_file_dialog) dialog_thread.start() self.selenium.click(field_selector) self.selenium.focus(field_selector) dialog_thread.join() def pop_email_for_client(self, email_address, fail_if_none=True, content_filter=None): retries = 6 while retries: message = self._pop_email_for_client_once(email_address, content_filter=content_filter) if message: return message else: retries -= 1 if retries == 0: if fail_if_none: self.fail('Email not received') time.sleep(5) def _pop_email_for_client_once(self, email_address, content_filter=None): from imapclient import IMAPClient message = None messages_to_delete = [] server = IMAPClient(IMAP_HOST, ssl=True) for m_id, parsed_headers, body_text in self.all_emails(server): if email_address in parsed_headers['To']: body_text = body_text.replace('\r', '') body_text = body_text.replace('=\n', '') if content_filter is None or content_filter in body_text: message = ( parsed_headers['From'], parsed_headers['To'], parsed_headers['Subject'], body_text ) messages_to_delete.append(m_id) server.delete_messages(messages_to_delete) return message def clear_email_for_address(self, email_address, content_filter=None): from imapclient import IMAPClient server = IMAPClient(IMAP_HOST, ssl=True) messages_to_delete = [] for m_id, parsed_headers, body_text in self.all_emails(server): if email_address in parsed_headers['To']: if content_filter is None or content_filter in body_text: messages_to_delete.append(m_id) server.delete_messages(messages_to_delete) def all_emails(self, server): server.login(IMAP_USERNAME, IMAP_PASSWORD) server.select_folder('INBOX') messages = server.search(['NOT DELETED']) response = server.fetch(messages, ['RFC822.TEXT', 'RFC822.HEADER']) parser = Parser() for m_id, m in response.items(): parsed_headers = parser.parsestr(m['RFC822.HEADER']) body_text = m['RFC822.TEXT'] yield (m_id, parsed_headers, body_text) ================================================ FILE: dirigible/fts/tests/test_2521_CodeEditor.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from selenium.webdriver.common.keys import Keys from textwrap import dedent from functionaltest import FunctionalTest, snapshot_on_error class Test_2521_CodeEditor(FunctionalTest): def test_code_editor_tabs_and_indents(self): # * Harold wants an editor with syntax coloring and other good stuff # instead of a boring textarea # * He logs in and creates a new sheet self.login_and_create_new_sheet() # * He notes that the code editor is an Ace editor (!) self.assertTrue( self.is_element_present('css=#id_usercode.ace_editor'), 'editor component not present' ) # * He plays around with the code editor and discovers that # tabs are converted to 4 spaces, and it autoindents original_code = self.get_usercode() self.get_element('id=id_usercode').click() self.human_key_press('a') self.human_key_press('\n') self.human_key_press(Keys.TAB) self.human_key_press('b') self.human_key_press('\n') self.human_key_press('c') self.human_key_press('\n') four_spaces = ' ' autoindent = four_spaces expected_code_after_typing = ( '{original_code}a\n' '{four_spaces}b\n' '{autoindent}c\n' ).format( original_code=original_code, four_spaces=four_spaces, autoindent=autoindent ) self.wait_for_usercode_editor_content(expected_code_after_typing) # ... undo works with self.key_down(Keys.CONTROL): self.human_key_press('z') with self.key_down(Keys.CONTROL): self.human_key_press('z') expected_code_after_undo = ( '{original_code}a\n' '{four_spaces}b\n' ).format( original_code=original_code, four_spaces=four_spaces, ) self.wait_for_usercode_editor_content(expected_code_after_undo) # ... and redo works with self.key_down(Keys.CONTROL): self.human_key_press('y') with self.key_down(Keys.CONTROL): self.human_key_press('y') self.wait_for_usercode_editor_content(expected_code_after_typing) # and typing normally again is fine self.human_key_press('abcabc') self.wait_for( lambda: 'abcabc' in self.get_usercode(), lambda: 'could not find abcabc in {}'.format(self.get_usercode()), ) @snapshot_on_error def test_code_editor_shows_errors(self): # * Harold makes mistakes when writing Python and wants Dirigible to # tell him about them so he can fix them error_locator = 'css=div.ace_gutter-cell.ace_error' def assert_error(line, message): self.wait_for_element_to_appear(error_locator) error = self.get_element(error_locator) self.assertEquals(error.text, str(line)) self.assertEquals(error.get_attribute('title'), message) # * He logs in and creates a new sheet self.login_and_create_new_sheet() # * He enters a value into the grid self.enter_cell_text(1, 1, 'herrrroooo') original_usercode = self.get_usercode() # * He enters some code that creates a syntax error self.prepend_usercode('import sys:') # ... and notes that an error appears in the code editor, and the value # in the grid is grey because the load_constants in the usercode was # never executed. self.wait_for_cell_shown_formula(1, 1, 'herrrroooo') assert_error(1, 'Syntax error at character 11') # * He refreshes the page, just because he's ornery and notes that the # grid is intact and the error is marked self.refresh_sheet_page() self.assert_cell_shown_formula(1, 1, 'herrrroooo') assert_error(1, 'Syntax error at character 11') # * He deletes his mistake and, the error indicator goes away and the # text ungreys self.enter_usercode(original_usercode) self.wait_for_cell_value(1, 1, 'herrrroooo') self.wait_for_element_presence(error_locator, False) # * Not satisfied, he tries a different type of error, this one after # the usercode loads the constants. original_usercode_lines = len(original_usercode.split('\n')) self.append_usercode('x = newvalue') # Once again, he notes that his grid survives, an error appears in the # right place. However, this time the cell value is not grey. self.wait_for_cell_value(1, 1, 'herrrroooo') assert_error( original_usercode_lines + 1, u"NameError: name \u2019newvalue\u2019 is not defined" ) def get_editor_selected_range(self): returned_dict = self.browser.execute_script(dedent( """ var selection = window.editor.getSelectionRange(); return ( selection.start.column + ", " + selection.start.row + ", " + selection.end.column + ", " + selection.end.row ); """ )) return eval(returned_dict) def assert_editor_line_visible(self, line): first_visible_row = self.browser.execute_script( "return window.editor.getFirstVisibleRow()" ) last_visible_row = self.browser.execute_script( "return window.editor.getLastVisibleRow()" ) self.assertLess(int(first_visible_row), line) self.assertGreater(int(last_visible_row), line) @snapshot_on_error def test_code_editor_find_function(self): # * Harold logs in and creates a new sheet self.login_and_create_new_sheet() # He enters some long and complicated usercode, which contains the string "123" at a well-known place, # and doesn't hit "save". code = "" for i in range(100): code += "a\n" code += "abc123def" for i in range(100): code += "a\n" self.enter_usercode(code, commit_change=False) # He hits ^F, and types 123 into the resulting dialog. self.get_element('id=id_usercode').click() with self.key_down(Keys.CONTROL): self.human_key_press('f') alert = self.browser.switch_to_alert() alert.send_keys(123) alert.accept() # The editor jumps to the "123" bit and selects it. self.assertEquals(self.get_editor_selected_range(), (3, 100, 6, 100)) # The line is, of course, visible. self.assert_editor_line_visible(100) @snapshot_on_error def test_code_editor_go_to_line_function(self): # * Harold logs in and creates a new sheet self.login_and_create_new_sheet() # He enters some long and complicated usercode and doesn't hit "save". code = "" for i in range(200): code += "a\n" self.enter_usercode(code, commit_change=False) # He hits ^L, and types 100 into the resulting dialog. self.get_element('id=id_usercode').click() with self.key_down(Keys.CONTROL): self.human_key_press('l') alert = self.browser.switch_to_alert() alert.send_keys(100) alert.accept() # The editor jumps to line 100 self.assertEquals(self.get_editor_selected_range(), (0, 99, 0, 99)) # The line is, of course, visible. self.assert_editor_line_visible(100) ================================================ FILE: dirigible/fts/tests/test_2525_LoginLogout.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from urlparse import urlparse from functionaltest import FunctionalTest, snapshot_on_error, Url class Test_2525_LoginLogout(FunctionalTest): user_count = 2 def assert_login_error_shown(self): self.assertEquals( self.get_text('id=id_login_error'), "The user name or password is incorrect. Please try again." ) def test_login_happy_path(self): # Harold logs in self.go_to_url(Url.LOGIN) # Finally, he enters the correct username and password. self.get_element('id=id_username').clear() self.get_element('id=id_password').clear() self.get_element('id=id_username').send_keys(self.get_my_username()) self.get_element('id=id_password').send_keys('p4ssw0rd') self.click_link('id_login') # He is taken to a page entitled "XXXX's Dashboard: Dirigible" at the site's root URL. self.assertEquals( self.browser.title, "{}'s Dashboard: Dirigible".format(self.get_my_username()) ) _, __, path, ___, ____, _____ = urlparse(self.browser.current_url) self.assertEquals(path, '/') # He sees links to the terms and conditions and suchlike at the bottom of the page. self.assert_has_useful_information_links() # On the page is a "Log Out" link self.assertEquals(self.get_text('id=id_logout_link'), "Log out") @snapshot_on_error def test_login(self): # Harold goes to a specific URL. self.go_to_url(Url.LOGIN) print('title') # He sees a web page with "Login: Dirigible" in the title bar. self.assertEquals(self.browser.title, 'Login: Dirigible') welcome_url = self.browser.current_url print('focus') # and notices that his cursor is in the username field self.assertTrue(self.is_element_focused('id=id_username')) print('userful info') # He sees links to the terms and conditions and suchlike at the bottom of the page. self.assert_has_useful_information_links() print('links') # He sees links to the terms and conditions and suchlike at the bottom of the page. # He notices a login link on the page and sees that it leads back to this page login_link = self.get_element('css=a#id_login_link') self.assertEqual(login_link.get_attribute('href'), Url.LOGIN) # There is also a "sign up" link that would take him to the signup page. signup_link = self.get_element('css=a#id_signup_link') self.assertEqual(signup_link.get_attribute('href'), Url.SIGNUP) print('inputs') # The page also contains places where he can enter a user name and a password, and # a login button. self.get_element('css=input#id_username') self.get_element('css=input#id_password') self.get_element('css=input#id_login[type=submit]') print('first click') # He enters neither, and clicks the login button. self.click_link('id_login') # He is taken back to a copy of the login page where he is additionally chided for # not entering his details. self.assert_login_error_shown() # He enters a username but no password self.get_element('id=id_username').send_keys('confused_user') self.click_link('id_login') # He is taken back to a copy of the login page where he is told he must enter a # password. The username is still there. self.assert_login_error_shown() self.assertEquals(self.get_value('id=id_username'), 'confused_user') # He enters a password but no username self.get_element('id=id_username').clear() self.get_element('id=id_password').send_keys('confused_pass') self.click_link('id_login') # He is taken back to a copy of the login page that patiently reminds him that he # should enter a username. The password is cleared self.assert_login_error_shown() self.assertEquals(self.get_value('id=id_username'), '') self.assertEquals(self.get_value('id=id_password'), '') # He enters the wrong username and password self.get_element('id=id_username').clear() self.get_element('id=id_password').clear() self.get_element('id=id_username').send_keys('wrong user') self.get_element('id=id_password').send_keys('wrong password') self.click_link('id_login') # He is taken back to a copy of the login page telling him that the username/password # combo wasn't recognised. username stays, password goes self.assert_login_error_shown() self.assertEquals(self.get_value('id=id_username'), 'wrong user') self.assertEquals(self.get_value('id=id_password'), '') # He corrects the username, but enters the wrong password username = self.get_my_username() self.get_element('id=id_username').clear() self.get_element('id=id_password').clear() self.get_element('id=id_username').send_keys(username) self.get_element('id=id_password').send_keys('wrong password') self.click_link('id_login') # He is taken back to a copy of the login page telling him that the username/password # combo wasn't recognised. username stays, password goes self.assert_login_error_shown() self.assertEquals(self.get_value('id=id_username'), username) self.assertEquals(self.get_value('id=id_password'), '') # He enters the correct password, but takes the opportunity to make the username # incorrect. self.get_element('id=id_username').clear() self.get_element('id=id_password').clear() self.get_element('id=id_username').send_keys('wrong user') self.get_element('id=id_password').send_keys('p4ssw0rd') self.click_link('id_login') # He is taken back to a copy of the login page telling him that the username/password # combo wasn't recognised. username stays, password goes self.assert_login_error_shown() self.assertEquals(self.get_value('id=id_username'), 'wrong user') self.assertEquals(self.get_value('id=id_password'), '') # Finally, he enters the correct username and password. self.get_element('id=id_username').clear() self.get_element('id=id_password').clear() self.get_element('id=id_username').send_keys(username) self.get_element('id=id_password').send_keys('p4ssw0rd') self.click_link('id_login') # He is taken to a page entitled "XXXX's Dashboard: Dirigible" at the site's root URL. self.assertEquals( self.browser.title, "{}'s Dashboard: Dirigible".format(username) ) _, __, path, ___, ____, _____ = urlparse(self.browser.current_url) self.assertEquals(path, '/') # He sees links to the terms and conditions and suchlike at the bottom of the page. self.assert_has_useful_information_links() # On the page is a "Log Out" link self.assertEquals(self.get_text('id=id_logout_link'), "Log out") # He follows it. self.click_link('id_logout_link') # He is taken back to the "Login: Dirigible" page he saw at the start of this FT. self.assertEquals(self.browser.current_url, welcome_url) self.assertEquals(self.browser.title, 'Login: Dirigible') # He logs in again self.get_element('id=id_username').send_keys(username) self.get_element('id=id_password').send_keys('p4ssw0rd') self.click_link('id_login') # He goes back to the login page and is presented with the option to login # and the page also includes 'My account' and 'Logout' links self.go_to_url(Url.LOGIN) self.get_element('css=input#id_username') self.get_element('css=input#id_password') self.assertEquals(self.get_text('id=id_logout_link'), "Log out") self.assertEquals(self.get_text('id=id_account_link'), "My account") # He is satisfied, and calls it a day. def test_legacy_dashboard_link_takes_you_to_root_url(self): harriet = self.get_my_usernames()[1] harold = self.get_my_username() harolds_dashboard_url = '/user/%s/' % (harold,) harriets_dashboard_url = '/user/%s/' % (harriet,) # Before logging in, Harold tries to access his own dashboard using the # old-style URL. # He gets sent to the root page. self.assert_sends_to_root_page(harolds_dashboard_url) # Before logging in, Harold tries to access Harriet's dashboard using the # old-style URL. # He gets redirected to the root page self.assert_sends_to_root_page(harriets_dashboard_url) # Before logging in, Harold also tries to access a non-existent user's dashboard using the # old-style URL. # He gets sent to the root page. self.assert_sends_to_root_page('/user/non-existent-user/') # After logging in, Harold tries to access his own dashboard using the # old-style URL. # He gets sent to the root page. self.login(username=harold) self.assert_sends_to_root_page(harolds_dashboard_url) # After logging in, Harold tries to access Harriet's dashboard using the # old-style URL. # He gets sent to the root page. self.assert_sends_to_root_page(harriets_dashboard_url) # After logging in, Harold also tries to access a non-existent user's dashboard using the # old-style URL. # He gets sent to the root page. self.assert_sends_to_root_page('/user/non-existent-user/') ================================================ FILE: dirigible/fts/tests/test_2528_CreateEditSheet.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from selenium.webdriver.common.keys import Keys from urlparse import urlparse from functionaltest import FunctionalTest, snapshot_on_error, Url class Test_2528_CreateEditSheet(FunctionalTest): user_count = 2 def assert_editing_cell(self, column, row): self.assertTrue(self.is_element_present( self.get_cell_editor_locator(column, row) )) @snapshot_on_error def test_create_edit_sheet(self): # * Harold logs in to Dirigible. self.login() # * On his dashboard, he notes an option to create a new spreadsheet. # He chooses it. self.assertEquals(self.get_text('id=id_create_new_sheet'), "Create new sheet...") self.click_link('id_create_new_sheet') # * He is taken to a web page which has a URL like /user/XXXX/sheet/ # where XXXX is his user name _, __, path, ___, ____, _____ = urlparse(self.browser.current_url) self.assertRegexpMatches(path, '/user/%s/sheet/[0-9]+/' % (self.get_my_username(),)) # * The page has a grid. print('check grid') self.assertTrue(self.is_element_present('id=id_grid')) # * He sees that the grid is a usable size (at least 100x100) print('check grid 2') self.wait_for_grid_to_appear() print('check width') self.assertTrue(self.get_element('id=id_grid').size['width'] >= 100) self.assertTrue(self.get_element('id=id_grid').size['height'] >= 100) # * Now that the grid is loaded (and so the sheet's name is too), he notices # that the title is something like: XXXX's sheet_name: Dirigible sheet_name = self.get_text('id=id_sheet_name') self.assertEquals( self.browser.title, "%s's %s: Dirigible" % (self.get_my_username(), sheet_name) ) # * He sees that the grid is reasonably layed out, and it has a sensible # number of rows and columns (at least 10x10), the columns having # headers A, B, C etc and the rows 1, 2, 3,... column_list_css = 'div.slick-header-column span.slick-column-name' def get_columns(): return self.browser.find_elements_by_css_selector(column_list_css) ## Check for 10 cols plus one header col print('check num cols') self.wait_for( lambda: len(get_columns()) >= 11, lambda: 'column count to become >= 11, was {}'.format(len(get_columns())), ) print('col positions') col_header_vertical_positions = [] for letter in 'ABCDEFG': column = self.get_element("css=div.slick-header-column[title={}]".format(letter)) col_header_vertical_positions.append(column.location['y']) self.assertEquals(len(set(col_header_vertical_positions)), 1) print('col positions horiz') col_header_horizontal_positions = [] for letter in 'ABCDEFG': column = self.get_element("css=div.slick-header-column[title={}]".format(letter)) col_header_horizontal_positions.append(column.location['x']) self.assertEquals( col_header_horizontal_positions, sorted(col_header_horizontal_positions) ) print('col headers') column_headers = [c.text for c in get_columns()] print(column_headers) self.assertEquals(''.join(column_headers[:8]), "ABCDEFG") # starts with one blank ## Check for 10 rows plus one header row print('rows') row_list_css = 'div.slick-row' rows = self.browser.find_elements_by_css_selector(row_list_css) ## SlickGrid handles the header row for us, so we don't have to check for it self.assertGreater(len(rows), 9) row_header_vertical_positions = [] for row in rows: row_header_vertical_positions.append(row.location['y']) self.assertEquals(row_header_vertical_positions, sorted(row_header_vertical_positions)) row_header_horizontal_positions = set() for row in rows: row_header_horizontal_positions.add(row.location['x']) self.assertEquals(len(row_header_horizontal_positions), 1) row_headers = ''.join(r.text for r in rows) self.assertEquals(row_headers[:9], "123456789") # * He enters "1" in A1. print('enter text') self.enter_cell_text(1, 1, '1') print('text entered') # * When he moves away from the cell, "1" is there print('clicking cell') self.click_on_cell(2, 2) print('cell clicked') print('wait for value') self.wait_for_cell_value(1, 1, '1') print('value waited') # * He enters "2" in A2. Similarly, this persists when he moves away. print('enter text') self.enter_cell_text(1, 2, '2') print('text entered') print('clicking cell') self.click_on_cell(2, 3) print('cell clicked') print('wait for value') self.wait_for_cell_value(1, 2, '2') print('value waited') # * He enters "=a1+A2" in A3. When he moves away, he sees "3" self.enter_cell_text(1, 3, '=11+22') self.click_on_cell(2, 4) self.wait_for_cell_value(1, 3, '33') # When he edits the cell again, he is presented with the formula as he entered it self.open_cell_for_editing(1, 3) self.wait_for_cell_editor_content('=11+22') # He moves the cursor left and right while editing and the edited cell does not move print('checking arrow keys') self.human_key_press(Keys.LEFT) self.human_key_press(Keys.RIGHT) self.assert_editing_cell(1, 3) self.open_cell_for_editing(3, 3) self.human_key_press(Keys.LEFT) self.human_key_press(Keys.LEFT) self.assert_editing_cell(3, 3) # * He enters "=a1+A2" (NB case!) in A4. When he moves away, he sees "3" self.enter_cell_text(1, 4, '=a1+A2') self.click_on_cell(2, 5) self.wait_for_cell_value(1, 4, '3') # When he edits the cell again, he is presented with the formula as he entered it self.open_cell_for_editing(1, 4) self.wait_for_cell_editor_content('=a1+A2') def test_new_sheet_not_logged_in(self): # * Harold goes to /new_sheet without logging in # * He gets redirected to the login page self.assert_sends_to_login_page(Url.NEW_SHEET) # * He logs in self.login(manually=True) # * ... and gets take to a new sheet url = urlparse(self.browser.current_url) self.assertEquals(url.netloc, urlparse(Url.ROOT).netloc) self.assertRegexpMatches(url.path, '/user/%s/sheet/[0-9]+/' % (self.get_my_username(),)) @snapshot_on_error def test_access_sheet_with_incorrect_user_id(self): ## Create sheet as user 1, for the rest of the test harriet = self.get_my_usernames()[1] harold = self.get_my_username() sheet_id = self.login_and_create_new_sheet(username=harriet) self.logout() harolds_broken_sheet_url = Url.sheet_page(harold, sheet_id) # Before logging in, Harold tries to access one of Harriet's sheets # using the wrong direct URL, with his username but the correct sheet ID. # He gets a 404 self.assert_HTTP_error(harolds_broken_sheet_url, 404) # After logging in, Harold tries to access one of Harriet's sheets # using the wrong direct URL, with his username but the correct sheet ID. # He gets a 404. self.login(username=harold) self.assert_HTTP_error(harolds_broken_sheet_url, 404) ================================================ FILE: dirigible/fts/tests/test_2529_HighlightErrorsInCells.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from functionaltest import FunctionalTest class Test_2529_HighlightErrorsInCells(FunctionalTest): def test_highlight_errors_in_cells(self): # * Harold logs in to Dirigible and creates a new sheet self.login_and_create_new_sheet() # * He enters "=lambda x:x + 1" in A1. self.enter_cell_text(2, 3, '=lambda x:x + 1') # * and notes that there is an error marked for that cell self.assert_cell_has_error( 2, 3, "FormulaError: Error in formula at position 10: unexpected ':'" ) # * He then enters '=my_value' in A2 self.enter_cell_text(4, 5, '=my_value') # * and notes that A2 complains that my_value is not defined self.assert_cell_has_error(4, 5, "NameError: name 'my_value' is not defined") # * He fixes his errors and notes that the error markers go away self.enter_cell_text(2, 3, '=lambda x->x + 1') self.enter_cell_text(4, 5, '=10') self.wait_for_cell_value(4, 5, '10') self.assert_cell_has_no_error(2, 3) self.assert_cell_has_no_error(4, 5) ================================================ FILE: dirigible/fts/tests/test_2531_DifficultStuffInCells.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from textwrap import dedent from functionaltest import FunctionalTest, snapshot_on_error, Url class Test_2531_DifficultStuffInCells(FunctionalTest): @snapshot_on_error def test_list_in_cell(self): # * Harold logs in to Dirigible and creates a new sheet self.login_and_create_new_sheet() # * He enters "=[1, 2, 3]" in A1. self.enter_cell_text(1, 1, '=[1, 2, 3]') # * When he moves away from the cell, "[1, 2, 3]" is there self.click_on_cell(2, 1) self.wait_for_cell_value(1, 1, '[1, 2, 3]') # * He enters a list comprehension in A2. self.enter_cell_text(1, 2, '=[x*2 for x in A1]') self.click_on_cell(2, 2) self.wait_for_cell_value(1, 2, '[2, 4, 6]') def test_dictionary_in_cell(self): # * Harold logs in to Dirigible and creates a new sheet self.login_and_create_new_sheet() # * He enters "={'key'->'value', 'key2'->3}" in A1. self.enter_cell_text(1, 1, "={'key' ->'value', 'key2'-> 3}") # * When he moves away from the cell, "{'key':'value', 'key2':3}" is there self.click_on_cell(2, 1) self.wait_for_cell_value(1, 1, repr({'key':'value', 'key2':3})) def test_object_in_cell(self): # * Harold logs in to Dirigible and creates a new sheet self.login_and_create_new_sheet() # * He enters "=object()" in A1. self.enter_cell_text(1, 1, '=object()') self.click_on_cell(2, 1) # * When he moves away from the cell, the obj is visible self.wait_for( lambda: self.get_cell_text(1, 1).startswith('') self.assert_input_roundtrips('fish&chips') self.assert_input_roundtrips('<') self.assert_input_roundtrips('>') self.assert_input_roundtrips('&') @snapshot_on_error def test_numeric_types(self): # * Harold logs in to Dirigible and creates a new sheet self.login_and_create_new_sheet() self.enter_cell_text(1, 1, '999') self.wait_for_cell_value(1, 1, '999') self.open_cell_for_editing(1, 1) self.wait_for_cell_editor_content('999') self.append_usercode("worksheet[1, 2].value = type(worksheet[1, 1].value)") self.wait_for_cell_value(1, 2, "") self.enter_cell_text(2, 1, '999.0') self.wait_for_cell_value(2, 1, '999.0') self.open_cell_for_editing(2, 1) self.wait_for_cell_editor_content('999.0') self.append_usercode("worksheet[2, 2].value = type(worksheet[2, 1].value)") self.wait_for_cell_value(2, 2, "") ================================================ FILE: dirigible/fts/tests/test_2532_LambdasInCells.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # import re import time from urlparse import urlparse from textwrap import dedent from browser_settings import SERVER_IP from functionaltest import FunctionalTest, snapshot_on_error class Test_2532_LambdasInCells(FunctionalTest): @snapshot_on_error def test_lambda_in_cell(self): # * Harold logs in to Dirigible and creates a new sheet self.login_and_create_new_sheet() # * He enters "=lambda x -> x + 1" in A1. self.enter_cell_text(1, 1, '=lambda x -> x + 1') # * He enters =A1(5) into B1 self.enter_cell_text(2, 1, '=A1(5)') # * and notes that the value in B1 is 6 self.wait_for_cell_value(2, 1, '6') ================================================ FILE: dirigible/fts/tests/test_2533_Numpy.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from functionaltest import FunctionalTest, snapshot_on_error class Test_2533_Numpy(FunctionalTest): @snapshot_on_error def test_numpy_tutorial_create_arrays(self): # * Harold wants to do the tutorial at http://www.scipy.org/Tentative_NumPy_Tutorial # in Dirigible # * He logs in and creates a new sheet self.login_and_create_new_sheet() # * At the start of the user code, he imports numpy self.prepend_usercode('import numpy') # * He creates a numoy array in cell A1 self.enter_cell_text(1, 1, '=numpy.array([2, 3, 4])') # * He sees A1 contains the string array([2, 3, 4]) self.wait_for_cell_value(1, 1, '[2 3 4]') # * He checks the type of the array in A2 self.enter_cell_text(1, 2, '=type(A1)') self.wait_for_cell_value(1, 2, "") # * He creates a 2-dimensional array in B1 self.enter_cell_text(2, 1, '=numpy.array([(1.5, 2, 3), (4, 5, 6)])') self.wait_for_cell_value(2, 1, "[[ 1.5 2. 3. ] [ 4. 5. 6. ]]") # * He has a look at some of the properties of the new array self.enter_cell_text(2, 2, '=B1.ndim') self.wait_for_cell_value(2, 2, "2") self.enter_cell_text(2, 3, '=B1.shape') self.wait_for_cell_value(2, 3, "(2, 3)") self.enter_cell_text(2, 4, '=B1.dtype') self.wait_for_cell_value(2, 4, "float64") self.enter_cell_text(2, 5, '=B1.itemsize') self.wait_for_cell_value(2, 5, "8") # * He creates an array of complex numbers self.enter_cell_text(3, 1, '=numpy.array([[1, 2], [3, 4]], dtype=numpy.complex)') self.wait_for_cell_value(3, 1, "[[ 1.+0.j 2.+0.j] [ 3.+0.j 4.+0.j]]") # * He creates arrays of zeros and ones self.enter_cell_text(4, 1, '=numpy.zeros((3, 4))') self.wait_for_cell_value(4, 1, "[[ 0. 0. 0. 0.] [ 0. 0. 0. 0.] [ 0. 0. 0. 0.]]") self.enter_cell_text(5, 1, '=numpy.ones((2, 3, 4), dtype=numpy.int16)') self.wait_for_cell_value(5, 1, "[[[1 1 1 1] [1 1 1 1] [1 1 1 1]] [[1 1 1 1] [1 1 1 1] [1 1 1 1]]]") # * He uses arange to create a list of numbers self.enter_cell_text(6, 1, '=numpy.arange(10, 30, 5)') self.wait_for_cell_value(6, 1, "[10 15 20 25]") # * He creates a sequence of floating point numbers self.enter_cell_text(7, 1, '=numpy.linspace(0, 2, 9)') self.wait_for_cell_value(7, 1, "[ 0. 0.25 0.5 0.75 1. 1.25 1.5 1.75 2. ]") # * He uses arange to create a very large array and notes that Dirigible displays it semi-intelligently self.enter_cell_text(8, 1, '=numpy.arange(10000)') self.wait_for_cell_value(8, 1, "[ 0 1 2 ..., 9997 9998 9999]") @snapshot_on_error def test_numpy_tutorial_basic_operations(self): # * Harold wants to do the tutorial at http://www.scipy.org/Tentative_NumPy_Tutorial # in Dirigible # * He logs in and creates a new sheet self.login_and_create_new_sheet() # * At the start of the user code, he imports numpy self.prepend_usercode('import numpy') # * He deducts one range from another self.enter_cell_text(1, 1, '=numpy.array([20, 30, 40, 50])') self.enter_cell_text(2, 1, '=numpy.arange(4)') self.enter_cell_text(3, 1, '=a1 - B1') self.wait_for_cell_value(3, 1, "[20 29 38 47]") # * He squares a range self.enter_cell_text(3, 2, '=B1**2') self.wait_for_cell_value(3, 2, "[0 1 4 9]") # * He takes the sine of a range self.enter_cell_text(3, 3, '=10*numpy.sin(a1)') self.wait_for_cell_value(3, 3, "[ 9.12945251 -9.88031624 7.4511316 -2.62374854]") # * Having decided that Dirigible supports the basic operations to his satisfaction, # he skips ahead to check that it works with complex numbers self.enter_cell_text(1, 1, '=numpy.ones(3, dtype=numpy.int32)') self.enter_cell_text(2, 1, '=numpy.linspace(0, numpy.pi, 3)') self.enter_cell_text(2, 2, '=b1.dtype.name') self.wait_for_cell_value(2, 2, "float64") self.enter_cell_text(3, 1, '=a1+b1') self.wait_for_cell_value(3, 1, "[ 1. 2.57079633 4.14159265]") self.enter_cell_text(3, 2, '=c1.dtype.name') self.wait_for_cell_value(3, 2, "float64") self.enter_cell_text(4, 1, '=numpy.exp(c1 * 1j)') self.wait_for_cell_value(4, 1, "[ 0.54030231+0.84147098j -0.84147098+0.54030231j -0.54030231-0.84147098j]") self.enter_cell_text(4, 2, '=d1.dtype.name') self.wait_for_cell_value(4, 2, "complex128") ================================================ FILE: dirigible/fts/tests/test_2534_JsonWorksheets.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # try: import simplejson as json except ImportError: import json from urllib import urlencode from urllib2 import urlopen from functionaltest import FunctionalTest, Url class Test_2534_JsonWorksheets_v0_1(FunctionalTest): def enable_json_api_for_sheet(self): self.selenium.click('id=id_security_button') self.wait_for_element_visibility('id=id_security_form', True) self.selenium.click('id=id_security_form_json_enabled_checkbox') self.selenium.type('id=id_security_form_json_api_key', self.get_my_username()) self.selenium.click('id=id_security_form_ok_button') self.wait_for_element_visibility('id=id_security_form', False) def test_simple_json(self): # * Harold wants to use Dirigible to run his spreadsheets using # a json-based rest API # * He logs in to Dirigible and creates a new sheet sheet_id = self.login_and_create_new_sheet() # * He enables json api access for the sheet self.enable_json_api_for_sheet() # * He enters some values and formulae self.enter_cell_text(1, 1, '5') self.enter_cell_text(1, 2, 'abc') self.enter_cell_text(2, 1, '6') self.enter_cell_text(3, 1, '=a1 + b1') self.wait_for_cell_value(3, 1, '11') # * He uses an API call to get the sheet as JSON sheet_content = json.load(urlopen(Url.api_url(self.get_my_username(), sheet_id), data=urlencode({'api_key': self.get_my_username()}))) expected = { 'name' : 'Sheet %s' % (sheet_id,), '1': { '1': 5, '2': 'abc' }, '2': { '1': 6, }, '3': { '1': 11 }, } self.assertEquals(sheet_content, expected) def test_simple_json_with_error(self): # * Harold wants to use Dirigible to run his spreadsheets using # a json-based rest API, and wants the error-handling to be # well-defined (if perhaps not ideal from a debugging perspective) # * He logs in to Dirigible and creates a new sheet sheet_id = self.login_and_create_new_sheet() # * He enables json api access for the sheet self.enable_json_api_for_sheet() # * He enters a single formula operating on a single value self.enter_cell_text(1, 1, '5') self.enter_cell_text(1, 2, '=1/A1') self.wait_for_cell_value(1, 2, '0.2') # * He uses an API call to get the sheet as JSON, passing in an override # value that he knows will cause an error get_params = urlencode({'A1':'0', 'api_key': self.get_my_username()}) url = '%s?%s' % (Url.api_url(self.get_my_username(), sheet_id), get_params) try: sheet_content = json.load(urlopen(url)) except Exception, e: self.fail(str(e)) # * He gets back nothing for the cell which generated an error, expected = { 'name' : 'Sheet %s' % (sheet_id,), '1': { '1': 0, }, } self.assertEquals(sheet_content, expected) def test_simple_json_with_overrides_get(self): # * Harold wants to use Dirigible to run his spreadsheets using # a json-based rest API, and override the formula of some cells # * He logs in to Dirigible and creates a new sheet sheet_id = self.login_and_create_new_sheet() # * He enables json api access for the sheet self.enable_json_api_for_sheet() # * He enters some values and formulae self.enter_cell_text(1, 1, '5') # A1 self.enter_cell_text(1, 2, 'abc') # A2 self.enter_cell_text(2, 1, '6') # B1 self.enter_cell_text(3, 1, '=a1 + b1') # C1 self.wait_for_cell_value(3, 1, '11') # * He uses an API call to get the sheet as JSON # but overrides the values of cells using GET params: # B1=11 # C2=A1 + 1 # this also causes cell C1 to change value, since it # depends on B1 get_params = urlencode({'2,1':'11', 'C2':'=A1 + 1', 'api_key': self.get_my_username()}) url = '%s?%s' % (Url.api_url(self.get_my_username(), sheet_id), get_params) try: sheet_content = json.load(urlopen(url)) except Exception, e: self.fail(str(e)) expected = { 'name' : 'Sheet %s' % (sheet_id,), '1': { '1': 5, '2': 'abc' }, '2': { '1': 11, }, '3': { '1': 16, '2': 6 }, } self.assertEquals(sheet_content, expected) def test_simple_json_with_overrides_post(self): # * Harold wants to use Dirigible to run his spreadsheets using # a json-based rest API, and override the formula of some cells # * He logs in to Dirigible and creates a new sheet sheet_id = self.login_and_create_new_sheet() # * He enables json api access for the sheet self.enable_json_api_for_sheet() # * He enters some values and formulae self.enter_cell_text(1, 1, '5') # A1 self.enter_cell_text(1, 2, 'abc') # A2 self.enter_cell_text(2, 1, '6') # B1 self.enter_cell_text(3, 1, '=a1 + b1') # C1 self.wait_for_cell_value(3, 1, '11') # * He uses an API call to get the sheet as JSON # but overrides the values of cells using POST params: # B1=11 # C2=A1 + 1 # this also causes cell C1 to change value, since it # depends on B1 url = Url.api_url(self.get_my_username(), sheet_id) post_params = urlencode({'2,1':'11', 'C2':'=A1 + 1', 'api_key': self.get_my_username()}) try: sheet_content = json.load(urlopen(url, data=post_params)) except Exception, e: self.fail(str(e)) expected = { 'name' : 'Sheet %s' % (sheet_id,), '1': { '1': 5, '2': 'abc' }, '2': { '1': 11, }, '3': { '1': 16, '2': 6 }, } self.assertEquals(sheet_content, expected) ================================================ FILE: dirigible/fts/tests/test_2535_RunWorksheetSerial.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from functionaltest import FunctionalTest class Test_2535_RunWorksheet(FunctionalTest): def test_run_worksheet_no_overrides(self): # * Harold logs in, creates a new sheet and notes its id self.login_and_create_new_sheet() target_sheet_url = self.browser.current_url # * and enters a few calculations and constants self.enter_cell_text(1, 1, '2') self.enter_cell_text(1, 2, '3') self.enter_cell_text(3, 3, '=A1 + A2') # * He creates another new sheet self.create_new_sheet() # * He enters =run_worksheet('http://...') into A1 self.enter_cell_text(1, 1, '=run_worksheet("%s")' % (target_sheet_url,)) # * He enters =A1[3, 3] into A2 and notes that self.enter_cell_text(1, 2, '=A1[3, 3].value') # the value from C3 in the first sheet appears there self.wait_for_cell_value(1, 2, '5') def test_run_worksheet_with_overrides(self): # * Harold logs in, creates a new sheet and notes its id self.login_and_create_new_sheet() target_sheet_url = self.browser.current_url # * and enters a few calculations and constants self.enter_cell_text(1, 1, '2') self.enter_cell_text(1, 2, '3') self.enter_cell_text(3, 3, '=A1 + A2') # * He creates another new sheet self.create_new_sheet() # * He enters a run_worksheet with overrides into A1 overrides = { (1, 1): 10, (1, 2): '43', (1, 3): '=str(A1)' } overrides_in_formula = repr(overrides).replace(':', '->') self.enter_cell_text(1, 1, '=run_worksheet("%s", %s)' % (target_sheet_url, overrides_in_formula)) # the value from C3 in the first sheet appears there # * He enters =A1[3, 3] into A2 and notes that self.enter_cell_text(1, 2, '=A1[3, 3].value') self.wait_for_cell_value(1, 2, '53') # * He also notes that A3 contains 10 self.enter_cell_text(2, 2, '=A1[1, 3].value') self.wait_for_cell_value(2, 2, '10') def test_run_worksheet_with_error(self): # * Harold logs in, creates a new sheet and notes its id self.login_and_create_new_sheet() target_sheet_url = self.browser.current_url # * and enters an error self.prepend_usercode('import sys:') # * He creates another new sheet self.create_new_sheet() # * He enters a run_worksheet with overrides into A1 overrides = { (1, 1): 10, (1, 2): '43', (1, 3): '=str(A1)' } overrides_in_formula = repr(overrides).replace(':', '->') self.enter_cell_text(1, 1, '=run_worksheet("%s", %s)' % (target_sheet_url, overrides_in_formula)) # ... and notes the error shown in A1 self.assert_cell_has_error(1, 1, 'Exception: run_worksheet: Syntax error at character 11') ================================================ FILE: dirigible/fts/tests/test_2536_ParallelFormulaExecution.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from textwrap import dedent import re try: import unittest2 as unittest except ImportError: import unittest from functionaltest import FunctionalTest, snapshot_on_error took_regex = re.compile('Took ([^s]+)s') class Test_2536_ParallelFormulaExecution(FunctionalTest): def get_last_recalc_time(self): match = took_regex.match(self.get_console_content()) return float(match.group(1)) @snapshot_on_error def test_formulae_executed_in_parallel(self): # * Harold logs in and creates a new sheet self.login_and_create_new_sheet() # * Harold enters some usercode to provide formulae that take time to calculate self.prepend_usercode(dedent(''' import time def slow_product(val1, val2): time.sleep(1) return val1 * val2 ''')) # * he enters a tree of dependent cell formula which use slow_product self.enter_cell_text(1, 3, '=slow_product(1, 2)') self.enter_cell_text(2, 3, '=slow_product(3, 4)') self.enter_cell_text(3, 3, '=slow_product(5, 6)') self.enter_cell_text(4, 3, '=slow_product(7, 8)') self.enter_cell_text(1, 2, '=slow_product(A3, B3)') self.enter_cell_text(2, 2, '=slow_product(C3, D3)') self.enter_cell_text(1, 1, '=slow_product(A2, B2)') # * and waits for the sheet to calculate self.wait_for_cell_value(1, 1, '40320', timeout_seconds=10) # * He notes the the recalculation was much faster than he would # expect if the formulae had been run in series # Note: There are 7 formulae in 3 dependency 'layers' so we expect # the recalc to take about 3sec. self.assertTrue( self.get_last_recalc_time() < 3.2, 'calculation took too long - %ss. Parallel broken?' % (self.get_last_recalc_time(),) ) @snapshot_on_error def test_run_worksheet_executed_in_parallel(self): # * Harold logs in and creates a blank worker sheet self.login_and_create_new_sheet() worker_sheet_url = self.browser.current_url # * He then goes to create a master sheet that makes several calls on the worker self.create_new_sheet() master_sheet_url = self.browser.current_url # * he enters formulae that use the worksheet in a number of run_worksheet calls run_worksheet_formula = '=run_worksheet("%s")[1, 1].value' % (worker_sheet_url,) for row in range(1, 5): self.enter_cell_text(1, row, run_worksheet_formula) # * Harold goes back to the worker and enters some usercode to make a recalc slow self.go_to_url(worker_sheet_url) self.wait_for_grid_to_appear() self.prepend_usercode(dedent(''' import time start = time.time() x = 1 while time.time() - start < 10: time.sleep(0.5) x += 1 worksheet[1, 1].value = x ''')) self.wait_for_spinner_to_stop(timeout_seconds=15) # * He now goes back to the master sheet, self.go_to_url(master_sheet_url) self.wait_for_grid_to_appear() # * He notes the the recalculation was much faster than he would # expect if the run_worksheets had been run in series self.wait_for_spinner_to_stop(timeout_seconds=15) self.assertTrue(self.get_last_recalc_time() < 19, 'calculation took too long. Parallel recalc broken?') ================================================ FILE: dirigible/fts/tests/test_2537_ErrorsInConsole.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from functionaltest import FunctionalTest from textwrap import dedent class Test_2537_ErrorsInConsole(FunctionalTest): def test_console(self): # * Harold logs in and creates a new sheet self.login_and_create_new_sheet() # * He notes that there is a console on the page self.is_element_present('id=id_console') original_usercode = self.get_usercode() # * He makes an error in usercode self.prepend_usercode(dedent(''' def fn(): x = new_value ''')) self.append_usercode(dedent(''' fn()''')) fn_call_line = len(self.get_usercode().split('\n')) expected_traceback = '''NameError: global name 'new_value' is not defined User code line %d User code line 2, in fn''' % (fn_call_line,) # * ... and notes that a useful traceback appears in the console self.wait_for_console_content(expected_traceback) # * He removes his error, saves his usercode and notes that the error is no longer shown self.enter_usercode(original_usercode) self.wait_for_console_content("") def test_formula_error_in_console(self): # * Harold logs in and creates a new sheet self.login_and_create_new_sheet() # * He enters some formulae that contain errors self.enter_cell_text(1, 1, '=1/0') self.enter_cell_text(3, 2, '=newvalue') self.enter_cell_text(2, 4, '={"a": 2}') self.assert_cell_has_error(1, 1, 'ZeroDivisionError: float division') # * He notes that the errors are reported in the console expected_tracebacks = ('''NameError: name 'newvalue' is not defined Formula '=newvalue' in C2''', '''ZeroDivisionError: float division Formula '=1/0' in A1''', '''FormulaError: Error in formula at position 6: unexpected ': ' Formula '={"a": 2}' in B4''') for expected_traceback in expected_tracebacks: self.wait_for_console_content(expected_traceback) # * He removes the errors and notes that the traceback is cleared self.enter_cell_text(1, 1, '=1') self.enter_cell_text(3, 2, '') self.enter_cell_text(2, 4, '={"a"-> 2}') self.wait_for_cell_value(1, 1, '1') self.wait_for_console_content("") def test_formula_errors_precede_usercode_errors_in_console(self): # * Harold logs in and creates a new sheet self.login_and_create_new_sheet() # * He enters a formula with an error self.enter_cell_text(1, 1, '=1/0') # * and breaks the usercode after the formulae are run self.append_usercode('x = y') error_line = len(self.get_usercode().split('\n')) # * and notes that the errors are reported in the expected order expected_traceback = '''ZeroDivisionError: float division Formula '=1/0' in A1 NameError: name 'y' is not defined User code line %d''' % (error_line,) self.wait_for_console_content(expected_traceback) ================================================ FILE: dirigible/fts/tests/test_2538_ShowStdoutInConsole.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from functionaltest import FunctionalTest from textwrap import dedent class Test_2538_ShowStdoutInConsole(FunctionalTest): def test_stdout_displayed_in_console(self): # * Harold logs in and creates a new sheet self.login_and_create_new_sheet() # He adds usercode to print a message self.prepend_usercode("print 'greetings'") self.append_usercode("print 'puny human'") self.wait_for_spinner_to_stop() expected = dedent(''' greetings puny human''')[1:] self.wait_for_console_content(expected) def test_output_interleaved_with_formula_errors(self): # * Harold logs in and creates a new sheet self.login_and_create_new_sheet() # * He enters usercode that executes evaluate_formulae and outputs a number of times self.enter_cell_text(1, 1, '=1/0') self.enter_usercode(dedent(''' for x in range(2): print 'at %d' % (x,) evaluate_formulae(worksheet) ''')) self.wait_for_spinner_to_stop() # * He notes that formula errors and output are correctly interleaved self.wait_for_console_content(dedent(''' at 0 ZeroDivisionError: float division Formula '=1/0' in A1 at 1 ZeroDivisionError: float division Formula '=1/0' in A1''')[1:]) # and that they are correctly coloured self.assertEquals( self.sanitise_console_content(self.selenium.get_eval("window.$('.console_output_text').text()")), dedent(''' at 0 at 1 ''')[1:] ) self.assertEquals( self.sanitise_console_content(self.selenium.get_eval("window.$('.console_error_text').text()")), dedent(''' ZeroDivisionError: float division Formula '=1/0' in A1 ZeroDivisionError: float division Formula '=1/0' in A1 ''')[1:] ) ================================================ FILE: dirigible/fts/tests/test_2540_FrontPage.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from functionaltest import FunctionalTest, Url from browser_settings import SERVER_IP import urllib2 import urlparse class Test_2540_FrontPage(FunctionalTest): def check_url_not_broken(self, url): try: response = urllib2.urlopen(url) except Exception, e: self.fail("Error getting url %s: %s" % (url, str(e))) def check_links_not_broken_for_tag_attribute(self, tag_xpath, attribute): num_tags = self.selenium.get_xpath_count(tag_xpath) base_file_location = self.browser.current_url for i in range(1, num_tags + 1): src = self.selenium.get_attribute("xpath=(%s)[%s]@%s" % (tag_xpath, i, attribute)) url = urlparse.urljoin(base_file_location, src) if not url.startswith("mailto:"): self.check_url_not_broken(url) def test_front_page_links(self): # Harold goes to Dirigible's root page self.go_to_url('http://%s/' % (SERVER_IP,)) # He finds a page with the title "Welcome to Dirigible" self.assertEquals(self.browser.title, 'Welcome to Dirigible') # The CSS files are all loaded correctly self.check_links_not_broken_for_tag_attribute("//link[@rel='stylesheet']", "href") # As are the images self.check_links_not_broken_for_tag_attribute("//img", "src") # And the links self.check_links_not_broken_for_tag_attribute("//a", "href") # He notes in particular that the "log in" link take him to the login page. front_page = self.browser.current_url login_url = self.selenium.get_attribute("xpath=//a[text() = 'Log in']@href") self.assert_sends_to_login_page(login_url) self.go_to_url(front_page) # ...and that the "Pricing", "Find out more", "Watch a video", "Help", "Terms & Conditions", # "Privacy Policy", and "Contact Us" take him to pages which have appropriate-looking titles. self.assert_page_title_contains( self.selenium.get_attribute("xpath=//a[@id='id_pricing_link']@href"), 'Dirigible Pricing' ) self.assert_page_title_contains( self.selenium.get_attribute("xpath=//a[img[@id='id_find_out_more']]@href"), 'About Dirigible' ) self.assert_page_title_contains( self.selenium.get_attribute("xpath=//a[img[@id='id_watch_a_video']]@href"), 'The Video: Dirigible' ) self.assert_page_title_contains( self.selenium.get_attribute("xpath=//a[text() = 'Help']@href"), 'Dirigible documentation' ) self.assert_has_useful_information_links() ================================================ FILE: dirigible/fts/tests/test_2544_403_404_and_500_pages.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from urlparse import urljoin from functionaltest import FunctionalTest, PAGE_LOAD_TIMEOUT, Url class Test_2544_404And500Pages(FunctionalTest): user_count = 2 def test_403(self): harriet = self.get_my_usernames()[1] self.login_and_create_new_sheet(harriet) sheet_url = self.browser.current_url self.logout() # * Harold goes to a page he should not have access to harold = self.get_my_username() self.login(harold) ## Older versions of Selenium (like the one we use for IE) signal errors ## with exceptions. Newer ones don't. We handle the former case by ## looking at the exception details; for the latter we rely on the checks ## of the page text below. try: self.selenium.open(sheet_url) except Exception, e: self.assertTrue("403" in str(e), "Error not a 403: %s" % (e,)) # * He gets an appropriate Dirigible-specific 403 page. self.selenium.wait_for_page_to_load(PAGE_LOAD_TIMEOUT) self.check_page_load(sheet_url) self.assertEquals(self.browser.title, "Access Forbidden: Dirigible") self.assertEquals(self.get_text("id=id_server_error_title"), "403 Access Forbidden") error_text = self.get_text("id=id_server_error_text") self.assertTrue("This page is private and belongs to someone else." in error_text) # There is also a link to the Dirigible home page, which he follows and discovers # that it works. self.click_link('id_link_home') self.assertEquals(self.browser.current_url, Url.ROOT) def test_404(self): # * Harold goes to a non-existent page url = urljoin(Url.ROOT, '/notaVALIDpage/') ## Older versions of Selenium (like the one we use for IE) signal errors ## with exceptions. Newer ones don't. We handle the former case by ## looking at the exception details; for the latter we rely on the checks ## of the page text below. try: self.selenium.open(url) except Exception, e: self.assertTrue("404" in str(e), "Error not a 404: %s" % (e,)) # * He gets an appropriate Dirigible-specific 404 page. self.selenium.wait_for_page_to_load(PAGE_LOAD_TIMEOUT) self.check_page_load(url) self.assertEquals(self.browser.title, "Page not found: Dirigible") self.assertEquals(self.get_text("id=id_server_error_title"), "404 Not Found") error_text = self.get_text("id=id_server_error_text") self.assertTrue("The page that you seek" in error_text) self.assertTrue("can't be found; lost in the clouds." in error_text) self.assertTrue("Please look somewhere else" in error_text) # There is a link to the Dirigible home page, which he follows and discovers # that it works. self.click_link('id_link_home') self.assertEquals(self.browser.current_url, Url.ROOT) def test_500(self): # * Harold somehow triggers an internal server error self.login_and_create_new_sheet() url = urljoin(self.browser.current_url, 'set_cell_formula/') ## Older versions of Selenium (like the one we use for IE) signal errors ## with exceptions. Newer ones don't. We handle the former case by ## looking at the exception details; for the latter we rely on the checks ## of the page text below. try: self.selenium.open(url) except Exception, e: self.assertTrue("500" in str(e), "Error not a 500: %s" % (e,)) # * He gets an appropriate Dirigible-specific 500 page. self.selenium.wait_for_page_to_load(PAGE_LOAD_TIMEOUT) self.check_page_load(url) self.assertEquals(self.browser.title, "Server error: Dirigible") self.assertEquals(self.get_text("id=id_server_error_title"), "500 Internal Server Error") error_text = self.get_text("id=id_server_error_text") self.assertTrue("Sorry, something has gone wrong!" in error_text) self.assertTrue("The Dirigible team have notified and will look into the problem ASAP." in error_text) # There is a link to the Dirigible home page, which he follows and discovers # that it works. self.click_link('id_link_home') self.assertEquals(self.browser.current_url, Url.ROOT) ================================================ FILE: dirigible/fts/tests/test_2545_PageResizeBehaviour.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from functionaltest import FunctionalTest, snapshot_on_error class Test_2545_PageResizeBehaviour(FunctionalTest): @snapshot_on_error def test_splitters(self): # Harold logs in and creates a new sheet. self.login_and_create_new_sheet() # He notes that there are splitter bars self.assertTrue(self.is_element_present('css=.hsplitbar')) self.assertTrue(self.is_element_present('css=.vsplitbar')) # He uses the vertical split bar to resize the grid to its minimum width self.selenium.drag_and_drop('css=.vsplitbar', '-4000, 0') self.assertAlmostEquals(self.selenium.get_element_width('id=id_left_column'), 0, delta=5) # and then increases its width a bit self.selenium.drag_and_drop('css=.vsplitbar', '40, 0') self.assertAlmostEquals(self.selenium.get_element_width('id=id_left_column'), 40, delta=5) # He uses the horizontal split bar to shrink the console self.selenium.drag_and_drop('css=.hsplitbar', '0, 2000') self.assertAlmostEquals(self.selenium.get_element_height('id=id_console_wrap'), 0, delta=5) # and then increases its height a bit self.selenium.drag_and_drop('css=.hsplitbar', '0, -20') self.assertAlmostEquals(self.selenium.get_element_height('id=id_console_wrap'), 20, delta=5) # He notes that his splitter positions are restored when he reloads the page self.refresh_sheet_page() self.wait_for_grid_to_appear() self.assertAlmostEquals(self.selenium.get_element_width('id=id_left_column'), 40, delta=5) self.assertAlmostEquals(self.selenium.get_element_height('id=id_console_wrap'), 20, delta=5) # He uses the vertical split bar to resize the right-hand column to its minimum width self.selenium.drag_and_drop('css=.vsplitbar', '4000, 0') self.assertAlmostEquals(self.selenium.get_element_width('id=id_right_column'), 0, delta=5) # and then increases its width a bit self.selenium.drag_and_drop('css=.vsplitbar', '-40, 0') self.assertAlmostEquals(self.selenium.get_element_width('id=id_right_column'), 40, delta=5) # He uses the horizontal split bar to shrink the editor to zero size, and notes that it disappears self.selenium.drag_and_drop('css=.hsplitbar', '0, -2000') self.wait_for_element_visibility('id=id_usercode', False) # He uses the horizontal split bar to make the editor 25px high, and notes that it is still hidden self.selenium.drag_and_drop('css=.hsplitbar', '0, 25') self.wait_for_element_visibility('id=id_usercode', False) # He uses the horizontal split bar to give the editor and its margins 50px of space, and notes # that it is visible and small self.selenium.drag_and_drop('css=.hsplitbar', '0, 25') self.wait_for_element_visibility('id=id_usercode', True) self.assertAlmostEquals(self.selenium.get_element_height('id=id_usercode'), 40, delta=5) @snapshot_on_error def test_grid_resize(self): # Harold logs in a creates a new sheet. self.login_and_create_new_sheet() # He sees that the bottom of the grid is a specific distance from the bottom of the window, # and that the top of the grid is a specific distance from the top of the window. He notes # those distances down. grid_bounds = self.get_element_bounds("id=id_grid") browser_page_height = int(self.selenium.get_eval("window.document.body.clientHeight")) browser_page_width = int(self.selenium.get_eval("window.document.body.clientWidth")) original_distance_to_bottom = browser_page_height - grid_bounds.bottom original_distance_to_top = grid_bounds.top # He resizes the window. self.selenium.get_eval("window.resizeTo(%s, %s)" % (browser_page_width - 10, browser_page_height - 10)) # He notes that the grid is still the same distances from the top and the bottom of the # window. def get_distance_to_bottom(): new_grid_bounds = self.get_element_bounds("id=id_grid") browser_page_height = int(self.selenium.get_eval("window.document.body.clientHeight")) return browser_page_height - new_grid_bounds.bottom self.wait_for( lambda: get_distance_to_bottom() == original_distance_to_bottom, lambda: "Distance to bottom to become %s (was %s)" % (original_distance_to_bottom, get_distance_to_bottom()) ) def get_distance_to_top(): return self.get_element_bounds("id=id_grid").top self.wait_for( lambda: get_distance_to_top() == original_distance_to_top, lambda: "Distance to top to become %s (was %s)" % (original_distance_to_top, get_distance_to_top()) ) ================================================ FILE: dirigible/fts/tests/test_2546_ListSheetsOnDashboard.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from functionaltest import FunctionalTest, SERVER_IP, snapshot_on_error import key_codes class Test_2546_ListSheetsOnDashboard(FunctionalTest): def rename_current_sheet(self, name): self.selenium.click('id=id_sheet_name') self.wait_for( lambda: self.is_element_present('id=edit-id_sheet_name'), lambda: 'edit sheetname textbox to appear') self.selenium.type('id=edit-id_sheet_name', name) self.human_key_press(key_codes.ENTER) self.wait_for_element_presence('id=saving-id_sheet_name', False) def assert_sheet_is_listed(self, sheet_id, sheet_name=None): if sheet_name is None: sheet_name = 'Sheet %s' % (sheet_id,) expected_url = '/user/%s/sheet/%s/' % (self.get_my_username(), sheet_id) link_text = self.get_text( 'css=a[href="%s"]' % (expected_url,)) # "xpath=//a[contains(@href, '%s')]" % (expected_url,)) self.assertEquals(link_text, sheet_name) @snapshot_on_error def test_list_exists(self): # * Harold logs in to Dirigible. self.login() # * He notes that he is being told that he has no sheets. self.assertTrue( self.is_element_present('id=id_no_sheets_message'), "Could not find 'no sheets' message" ) # * He decides that he wants one, so he clicks on a button to create it. sheet1_id = self.create_new_sheet() # * He clicks 'my account' and goes back to dashboard page # * His new sheet is listed there, with a link to it self.click_link('id_account_link') self.assert_sheet_is_listed(sheet1_id) # He notes that the "no sheets" message is absent. self.assertFalse( self.is_element_present('id=id_no_sheets_message'), "Found 'no sheets' message when it wasn't expected" ) # * He clicks new sheet again sheet2_id = self.create_new_sheet() # * He renames the second sheet self.rename_current_sheet('Snarf') # * He clicks 'my account' and goes back to dashboard page self.click_link('id_account_link') # * Both sheets are listed there self.assert_sheet_is_listed(sheet1_id) self.assert_sheet_is_listed(sheet2_id, 'Snarf') ================================================ FILE: dirigible/fts/tests/test_2547_EnterDataQuickly.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # import time from functionaltest import FunctionalTest, snapshot_on_error class Test_2547_EnterDataQuickly(FunctionalTest): @snapshot_on_error def test_enter_data_quickly(self): # * Harold logs in and creates a new sheet self.login_and_create_new_sheet() # * He enters 20 numbers quickly for row in range(1, 21): self.enter_cell_text_unhumanized(1, row, "%s" % (row,)) # * He checks that they are all there. for row in range(1, 21): self.wait_for_cell_value(1, row, "%s" % (row,)) @snapshot_on_error def test_enter_data_quickly_in_batches(self): # Running the above test made it clear that it was significantly more # likely to drop the last number than any others, so we added the below # to increase the likelihood of that particular failure mode. # * Harold logs in and creates a new sheet self.login_and_create_new_sheet() # * He enters 20 numbers quickly, taking a brief rest every five numbers for row in range(1, 21): self.enter_cell_text_unhumanized(1, row, "%s" % (row,)) if row % 5 == 0: time.sleep(2) # * He checks that they are all there. for row in range(1, 21): self.wait_for_cell_value(1, row, "%s" % (row,)) ================================================ FILE: dirigible/fts/tests/test_2548_UserCode.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from textwrap import dedent from functionaltest import FunctionalTest, snapshot_on_error class Test_2548_UserCode(FunctionalTest): @snapshot_on_error def test_editor_presence(self): # * Harold logs in to Dirigible and creates a new sheet self.login_and_create_new_sheet() # * He notices that there is an Ace code editor on the page, which he # has inexplicably not noticed before. self.assertTrue( self.is_element_present('css=#id_usercode.ace_editor') ) # * In the editor, there is some interesting-looking code self.assertEquals( self.get_usercode().strip(), dedent( ''' load_constants(worksheet) # Put code here if it needs to access constants in the spreadsheet # and to be accessed by the formulae. Examples: imports, # user-defined functions and classes you want to use in cells. evaluate_formulae(worksheet) # Put code here if it needs to access the results of the formulae. ''').strip() ) @snapshot_on_error def test_preformula_usercode(self): # * Harold wants to create a function to use in a cell's formula # * He logs in to Dirigible and creates a new sheet. self.login_and_create_new_sheet() # * He enters the following code into the usercode before the formula code: #{{{ #def addA1(value): # return value + worksheet.A1 #}}} self.enter_usercode(dedent(''' load_constants(worksheet) def addA1(value): return value + worksheet[1, 1].value evaluate_formulae(worksheet) ''')) # * He enters '1' into A1 self.enter_cell_text(1, 1, '1') # * In cell A4, he enters "=addA1(5)", and gets the result 6. self.enter_cell_text(1, 4, '=addA1(5)') self.wait_for_cell_value(1, 4, '6') @snapshot_on_error def test_postformula_usercode(self): # * Harold wants to specify code to run after his formulae have been calculated # * He logs in to Dirigible and creates a new sheet. self.login_and_create_new_sheet() # * He enters 2 into cell A3 self.enter_cell_text(1, 3, '2') # * He adds an appropriate line to the end of the usercode, after the # evaluate_formulae call, and applies the change: self.enter_usercode(dedent(''' load_constants(worksheet) evaluate_formulae(worksheet) worksheet[1, 5].value = 10 + worksheet[1, 3].value ''')) # * He notes that the value "13" appears in A5. self.wait_for_cell_value(1, 5, '12') ================================================ FILE: dirigible/fts/tests/test_2549_InterruptedRecalculations.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # import time from textwrap import dedent from functionaltest import FunctionalTest, snapshot_on_error class Test_2549_InterruptedRecalculations(FunctionalTest): @snapshot_on_error def test_interrupted_recalc_coming_back_handled_correctly(self): # Harold logs on and creates a sheet self.login_and_create_new_sheet() # Harold enters a long-running recalc that finishes by # setting a value in the grid self.enter_usercode(dedent(''' import time time.sleep(20) worksheet[1, 5].value = 1 ''')) # While it is running, he realises that there is a shorter # algorithm, so he tries that instead. time.sleep(1) self.enter_usercode(dedent(''' worksheet[1, 5].value = 2 ''')) # Even though the initial longer-running recalc finishes # after the second one, the result he sees is from the # second one; he concludes that the results of the first # one must have been thrown away. self.wait_for_cell_value(1, 5, '2') # To confirm this to himself, he waits until the first # recalc has absolutely definitely finished, and confirms # that its results never appear in the grid. time.sleep(30) self.wait_for_cell_value(1, 5, '2') # He then refreshes his web page to make sure that the # correct version is current in the database. self.refresh_sheet_page() self.wait_for_grid_to_appear() self.wait_for_cell_value(1, 5, '2') ================================================ FILE: dirigible/fts/tests/test_2550_EditableSheetName.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from functionaltest import FunctionalTest from textwrap import dedent import key_codes class Test_2550_EdittableSheetName(FunctionalTest): def test_sheet_name_edittable(self): # * Harold logs in and creates a new sheet self.login_and_create_new_sheet() # * He notes that the sheet has a name of the form 'Sheet #xx' # where xx is the sheet id sheet_id = self.browser.current_url.split(r'/')[-2] self.assertEquals( self.get_text('id=id_sheet_name'), 'Sheet %s' % (sheet_id,)) # * He mouses over the sheet name and notes that the appearance # changes to indicate that it's editable self.selenium.mouse_over('id=id_sheet_name') self.wait_for( lambda: self.get_css_property('#id_sheet_name', 'background-color') == '#D1D2D4', lambda: 'sheet name background to darken') # * He moves the mouse away - it changes back self.selenium.mouse_out('id=id_sheet_name') self.wait_for( lambda: self.get_css_property('#id_sheet_name', 'background-color') == 'transparent', lambda: 'sheet name background to return to normal') # * He clicks on the sheet name, the sheeetname edit textarea appears, # and remains when the mouse moves away self.selenium.click('id=id_sheet_name') self.wait_for( lambda: self.is_element_present('id=edit-id_sheet_name'), lambda: 'editable sheetname to appear') self.selenium.mouse_out('id=id_sheet_name') self.assertTrue(self.is_element_present('id=edit-id_sheet_name')) # * He types a new sheetname 'margarita', hits return. self.selenium.type('id=edit-id_sheet_name', 'margarita') self.human_key_press(key_codes.ENTER) # The sheetname is modified self.wait_for( lambda: self.get_text('id=id_sheet_name') == 'margarita', lambda: 'sheet name to be updated' ) # and the title to the page becomes "user's sheet_name: Dirigible" self.assertEquals(self.browser.title, "%s's %s: Dirigible" % (self.get_my_username(), 'margarita')) # * He refreshes the page self.refresh_sheet_page() # The new sheetname persists self.wait_for( lambda: self.get_text('id=id_sheet_name') == 'margarita', lambda: 'sheet name to be updated' ) # and the title to the page remains "user's sheet_name: Dirigible" self.wait_for( lambda: self.browser.title == "%s's %s: Dirigible" % (self.get_my_username(), 'margarita'), lambda: 'page title to update' ) def test_changing_sheet_name_should_not_interrupt_recalc_but_still_succeeds(self): # * Harold logs in and creates a new sheet self.login_and_create_new_sheet() # * He enters usercode that takes a long time to run self.enter_cell_text(1, 1, '') self.enter_usercode(dedent(''' import time time.sleep(20) worksheet[1, 1].value = 'recalced' ''')) # * While the recalc is running, he changes the sheet name self.selenium.click('id=id_sheet_name') self.wait_for( lambda: self.is_element_present('id=edit-id_sheet_name'), lambda: 'editable sheetname to appear') self.selenium.type('id=edit-id_sheet_name', 'new sheet name') self.human_key_press(key_codes.ENTER) # * The recalculation finishes normally and his sheet has a new name self.wait_for_cell_value(1, 1, 'recalced', timeout_seconds=21) self.assertEquals(self.get_text('id=id_sheet_name'), 'new sheet name') # * he checks that the new sheetname sticks after a page refresh self.refresh_sheet_page() self.assertEquals(self.get_text('id=id_sheet_name'), 'new sheet name') ================================================ FILE: dirigible/fts/tests/test_2554_SlicingInFormulae.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from functionaltest import FunctionalTest class Test_2554_SlicingInFormulae(FunctionalTest): def test_formulas_work_properly(self): # * Harold logs in to Dirigible and creates a new sheet self.login_and_create_new_sheet() # Harold REALLY likes slicing things. We should ensure that # he can indulge his axe-murderer side self.enter_cell_text(1, 1, '=[0, 10, 20, 30, 40]') self.enter_cell_text(1, 2, '=A1[1->3]') self.wait_for_cell_value(1, 2, '[10, 20]') self.enter_cell_text(2, 1, 'a string that really needs to be sliced') self.enter_cell_text(2, 2, '=B1[->10]') self.wait_for_cell_value(2, 2, 'a string t') self.enter_cell_text(3, 2, '=B1[9->]') self.wait_for_cell_value(3, 2, 'that really needs to be sliced') self.enter_cell_text(4, 2, '=B1[-8->]') self.wait_for_cell_value(4, 2, 'e sliced') self.enter_cell_text(5, 2, '=B1[4->->3]') self.wait_for_cell_value(5, 2, 'rgh ayesoele') ================================================ FILE: dirigible/fts/tests/test_2556_BrokenUserCode.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from functionaltest import FunctionalTest, snapshot_on_error class test_2556_BrokenUsercode(FunctionalTest): @snapshot_on_error def test_newly_entered_formulae_appear_grey_then_results_in_black(self): # * Harold logs in to Dirigible and creates a new sheet self.login_and_create_new_sheet() # make recalc slow, so that we can be sure to see cells # passing through their intermediate 'sent to server # but not yet evaluated' state # Set 2,2 to ready to be sure this usercode change is made before # proceeding. self.prepend_usercode('import time\ntime.sleep(20.0)\nworksheet[2,2].formula="ready"') self.wait_for_cell_value(2, 2, 'ready', timeout_seconds=22) # check intermediate state then final state after entering a formula self.enter_cell_text(1, 2, '=123') self.wait_for_cell_shown_formula(1, 2, '=123', timeout_seconds=19) self.wait_for_cell_value(1, 2, '123', timeout_seconds=30) def test_broken_usercode_returns_as_much_as_it_can(self): # * Harold logs in to Dirigible and creates a new sheet self.login_and_create_new_sheet() original_usercode = self.get_usercode() # * He enters a constant and a formula in the sheet self.enter_cell_text(1, 1, 'abc') self.enter_cell_text(1, 2, '=123') self.wait_for_cell_value(1, 2, '123') # * He adds a usercode error before the constants get calculated self.prepend_usercode('x=1/0') self.enter_cell_text(2, 1, 'def') self.enter_cell_text(2, 2, '=456') self.wait_for_cell_shown_formula(2, 2, '=456') self.assert_cell_shown_formula(1, 1, 'abc') self.assert_cell_shown_formula(1, 2, '=123') self.assert_cell_shown_formula(2, 1, 'def') self.assert_cell_shown_formula(2, 2, '=456') # * He adds a usercode error between constants and formulae def insert(text): insertion_point = original_usercode.find('evaluate_formulae(worksheet)') return "%s\n%s\n%s" % ( original_usercode[:insertion_point], text, original_usercode[insertion_point:], ) usercode = insert('x=1/0\n') self.enter_usercode(usercode) self.enter_cell_text(3, 1, 'ghi') self.enter_cell_text(3, 2, '=789') self.wait_for_cell_shown_formula(3, 2, '=789') self.assertEquals(self.get_cell_text(1, 1), 'abc') self.assert_cell_shown_formula(1, 2, '=123') self.assertEquals(self.get_cell_text(2, 1), 'def') self.assertEquals(self.get_cell_text(2, 2), '=456') self.assertEquals(self.get_cell_text(3, 1), 'ghi') self.assertEquals(self.get_cell_text(3, 2), '=789') # * He adds a usercode error after formulae self.enter_usercode(original_usercode) self.append_usercode('x=1/0') self.enter_cell_text(4, 1, 'jkl') self.enter_cell_text(4, 2, '=100') self.wait_for_cell_value(4, 2, '100') self.assertEquals(self.get_cell_text(1, 1), 'abc') self.assertEquals(self.get_cell_text(1, 2), '123') self.assertEquals(self.get_cell_text(2, 1), 'def') self.assertEquals(self.get_cell_text(2, 2), '456') self.assertEquals(self.get_cell_text(3, 1), 'ghi') self.assertEquals(self.get_cell_text(3, 2), '789') self.assertEquals(self.get_cell_text(4, 1), 'jkl') self.assertEquals(self.get_cell_text(4, 2), '100') # \o/ hooray! ================================================ FILE: dirigible/fts/tests/test_2557_ClickAwaySavesUsercode.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from functionaltest import FunctionalTest class Test_2557_ClickAwaySavesUsercode(FunctionalTest): def test_blur_on_edit_textarea_saves_usercode(self): # * Harold logs in to Dirigible and creates a new sheet self.login_and_create_new_sheet() # * He enters some usercode that sets A1 to 4 self.selenium.get_eval('window.editor.focus()') self.enter_usercode('worksheet[1, 1].value = 4') # * He does something to cause a blur event on the editarea textbox ## We use fire_event because Selenium does not fire the full event stack ## for clicks. If this changes in future Selenium versions, it would be nice ## to expand this test to check behaviour for clicking on cells, links and ## edit sheet name self.selenium.get_eval("window.editor.blur()") # * ... and notes that A1 contains 4 self.wait_for_cell_value(1, 1, '4') ================================================ FILE: dirigible/fts/tests/test_2558_MoreCellsByDefault.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from functionaltest import FunctionalTest class Test_2558_MoreCellsByDefault(FunctionalTest): def test_more_cells_by_default(self): # * Harold logs in and creates a new sheet self.login_and_create_new_sheet() # * He notes that the sheet he is presented with has # an elegant sufficiency of rows and columns (1000 rows and 702 # (up to ZZ) columns) self.scroll_cell_row_into_view(1, 1000) row_list_xpath = '//div[contains(@class, "slick-row") and @row=999]' row_count = self.selenium.get_xpath_count(row_list_xpath) self.assertEquals(row_count, 1, 'fewer than 1000 rows') self.scroll_cell_row_into_view(52, 1) column_list_xpath = '//div[@class="slick-cell c52"]' column_count = self.selenium.get_xpath_count(column_list_xpath) self.assertTrue(column_count >= 1, 'fewer than AZ columns') self.scroll_cell_row_into_view(1, 1) # * The last cell on the grid accepts input as expected self.enter_cell_text(52, 1000, 'end of the sheet') # * He notes that, since he just typed on the bottom row, # the cell stays in edit mode, so he has to click away # to finish the edit self.click_on_cell(51, 999) self.wait_for_cell_value(52, 1000, 'end of the sheet') ================================================ FILE: dirigible/fts/tests/test_2559_FitEditorToCells.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from functionaltest import FunctionalTest class Test_2559_FitEditorToCells(FunctionalTest): def test_editor_fits_cells(self): # * Harold logs in and creates a new sheet self.login_and_create_new_sheet() cell_locator = self.get_cell_locator(3, 3) cell_width = self.selenium.get_element_width(cell_locator) cell_height = self.selenium.get_element_height(cell_locator) # * He edits a cell and notes that the editor fits the # cell exactly self.open_cell_for_editing(3, 3) editor_locator = 'css=input.editor-text' editor_width = self.selenium.get_element_width(editor_locator) editor_height = self.selenium.get_element_height(editor_locator) self.assertTrue(cell_width - editor_width <= 6, "cell width: %d, editor width: %d" % (cell_width, editor_width)) self.assertTrue(cell_height - editor_height <= 6, "cell height: %d, editor height: %d" % (cell_height, editor_height)) ================================================ FILE: dirigible/fts/tests/test_2562_ErrorInCellShouldBeClearedByConstants.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from functionaltest import FunctionalTest class test_2562_ErrorInCellShouldBeClearedByConstants(FunctionalTest): def test_ConstantsClearCellErrors(self): # * Harold logs in to Dirigible and creates a new sheet self.login_and_create_new_sheet() # * He enters and error in A1. self.enter_cell_text(1, 1, '=1/0') self.assert_cell_has_error(1, 1, "ZeroDivisionError: division by zero") # * when he overwrites the cell content with a constant # the error disappears self.enter_cell_text(1, 1, '123') self.assert_cell_has_no_error(1, 1) # hooray! ================================================ FILE: dirigible/fts/tests/test_2565_JSONAPIAuth.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from __future__ import with_statement try: import json except ImportError: import simplejson as json from urllib import urlencode from urllib2 import urlopen, HTTPError from urlparse import urljoin, urlparse # these can't be imported on non-windows machines, but we need to import test # modules on our *nix integration master without running them, to run the build try: from win32clipboard import OpenClipboard, GetClipboardData, CloseClipboard import win32con except: pass import key_codes from functionaltest import FunctionalTest, PAGE_LOAD_TIMEOUT, Url class Test_2565_JSONAPIAuth(FunctionalTest): def test_json_api_auth(self): # Harold wants to make sure that people only have JSON access to his sheets # when he has explicitly granted it. # * He logs in to Dirigible and creates a new sheet sheet_id = self.login_and_create_new_sheet() base_json_url = urljoin(self.browser.current_url, 'v0.1/json/') # * He enters some values and formulae self.enter_cell_text(1, 1, '5') # * He tries to use an API call to get the sheet as JSON, but gets a 403 error. with self.assertRaises(HTTPError) as mngr: urlopen(base_json_url) self.assertEquals(mngr.exception.code, 403) # * Looking around at the sheet page, he notices a "Security" button. self.wait_for_element_to_appear('id=id_security_button') # * He sees that the mouseover text on the button indicates that the JSON API is not enabled self.assertTrue( 'JSON API disabled' in self.selenium.get_attribute('css=#id_security_button@title') ) self.assertTrue( 'JSON API disabled' in self.selenium.get_attribute('css=#id_security_button@alt') ) # * He clicks the button. self.selenium.click('id=id_security_button') # * A dialog appears; there is an unchecked toggle saying "Allow JSON API access" self.wait_for_element_visibility('id=id_security_form', True) self.wait_for_element_visibility('id=id_security_form_save_error', False) self.wait_for_element_visibility('id=id_security_form_json_enabled_checkbox', True) self.wait_for_element_visibility('id=id_security_form_json_api_key', True) self.wait_for_element_visibility('id=id_security_form_json_api_url', True) self.assertFalse(self.is_element_enabled('id_security_form_json_api_key')) self.assertFalse(self.is_element_enabled('id_security_form_json_api_url')) self.assertEquals( self.get_text('css=label[for="id_security_form_json_enabled_checkbox"]'), 'Allow JSON API access' ) self.assertEquals(self.selenium.get_value('id=id_security_form_json_enabled_checkbox'), 'off') # * ... and OK and Cancel buttons self.wait_for_element_visibility('id=id_security_form_ok_button', True) self.wait_for_element_visibility('id=id_security_form_cancel_button', True) # * He checks it. He notices a textbox giving him an "API key", self.selenium.click('id=id_security_form_json_enabled_checkbox') self.assertTrue(self.is_element_enabled('id_security_form_json_api_key')) api_key = self.selenium.get_value('id=id_security_form_json_api_key') api_url = self.selenium.get_value('id=id_security_form_json_api_url') # * He also notices that when he clicks on the URL text field, the entire field is selected ## The focus call is to appease Chrome self.selenium.focus('id=id_security_form_json_api_url') self.selenium.click('id=id_security_form_json_api_url') # our 'caret' plugin appears to have a problem getting the selection # range for fields that are not editable, such as the json api url. # Consequently, we have to check the selection by copying this # text, and checking the clipboard content. with self.key_down(key_codes.CTRL): self.human_key_press(key_codes.LETTER_C) def get_clipboard_text(): OpenClipboard() text = GetClipboardData(win32con.CF_TEXT) CloseClipboard() return text self.wait_for( lambda: get_clipboard_text() == api_url, lambda: 'bad clipboard text, was: %s' % (get_clipboard_text(),) ) # * nothing appears outside the JSON API dialog box yet though. self.assertTrue( 'JSON API disabled' in self.selenium.get_attribute('css=#id_security_button@title') ) self.assertTrue( 'JSON API disabled' in self.selenium.get_attribute('css=#id_security_button@alt') ) # * He ignores all of the key stuff, presses Cancel self.selenium.click('id=id_security_form_cancel_button') # * He notices that the form disappears and that the icon still indicates that the JSON API is disabled self.wait_for_element_visibility('id=id_security_form', False) self.assertTrue( 'JSON API disabled' in self.selenium.get_attribute('css=#id_security_button@title') ) self.assertTrue( 'JSON API disabled' in self.selenium.get_attribute('css=#id_security_button@alt') ) # but he just tries accessing the JSON URL without a key again # * He gets 403 again. with self.assertRaises(HTTPError) as mngr: urlopen(base_json_url) self.assertEquals(mngr.exception.code, 403) # * and he also gets 403 when he uses the API Key that was displayed with self.assertRaises(HTTPError) as mngr: urlopen(base_json_url, urlencode({'api_key': api_key})) self.assertEquals(mngr.exception.code, 403) # * He half-realises what the problem is, opens the dialog again, checks the box, and presses OK self.selenium.click('id=id_security_button') self.wait_for_element_visibility('id=id_security_form', True) self.wait_for_element_visibility('id=id_security_form_save_error', False) self.assertEquals(self.selenium.get_value('id=id_security_form_json_enabled_checkbox'), 'off') self.selenium.click('id=id_security_form_json_enabled_checkbox') self.assertTrue(self.is_element_enabled('id_security_form_json_api_key')) self.assertTrue(self.is_element_enabled('id_security_form_json_api_url')) api_url = self.selenium.get_value('css=#id_security_form_json_api_url') self.selenium.click('id=id_security_form_ok_button') self.wait_for_element_visibility('id=id_security_form', False) #* He now sees the toolbar indicates that the JSON API is enabled for this sheet self.assertTrue( 'JSON API enabled' in self.selenium.get_attribute('css=#id_security_button@title') ) self.assertTrue( 'JSON API enabled' in self.selenium.get_attribute('css=#id_security_button@alt') ) # * Not trusting the memory of his browser, he opens the dialog again self.selenium.click('id=id_security_button') self.wait_for_element_visibility('id=id_security_form', True) self.wait_for_element_visibility('id=id_security_form_save_error', False) self.assertEquals(self.selenium.get_value('id=id_security_form_json_enabled_checkbox'), 'on') # * and immediately presses Cancel self.selenium.click('id=id_security_form_cancel_button') # * He is surprised and delighted to see that his sheet is still JSON-enabled self.assertTrue( 'JSON API enabled' in self.selenium.get_attribute('css=#id_security_button@title') ) self.assertTrue( 'JSON API enabled' in self.selenium.get_attribute('css=#id_security_button@alt') ) expected_url = "%s%s?api_key=%s" % ( self.selenium.browserURL[:-1], urlparse(Url.api_url(self.get_my_username(), sheet_id)).path, api_key ) self.assertEquals(api_url, expected_url) # .. despite this helpful link, he tries again with the wrong API key with self.assertRaises(HTTPError) as mngr: urlopen(base_json_url, urlencode({'api_key': 'abcd1234-123dfe'})) # * He gets a 403 self.assertEquals(mngr.exception.code, 403) # * Frustrated, he tries again with the right API key. response = urlopen(base_json_url, urlencode({'api_key': api_key})) # * He gets the data he expected. json_data = json.load(response) self.assertEquals(json_data['1']['1'], 5) # * He changes the API key in the JSON API dialog. self.selenium.click('id=id_security_button') self.wait_for_element_visibility('id=id_security_form', True) self.wait_for_element_visibility('id=id_security_form_save_error', False) old_api_url = self.selenium.get_value('css=#id_security_form_json_api_url') self.selenium.type('id=id_security_form_json_api_key', 'some_new_api_ke') self.selenium.focus('id=id_security_form_json_api_key') # He sees that the api url is updated with every keystroke self.human_key_press(key_codes.END) # Move IE insert point to the end self.human_key_press(key_codes.LETTER_Y) self.assertEquals( self.selenium.get_value('css=#id_security_form_json_api_url'), old_api_url.replace(api_key, 'some_new_api_key') ) self.selenium.click('id=id_security_form_ok_button') self.wait_for_element_visibility('id=id_security_form', False) # * He tries again, using the old key with self.assertRaises(HTTPError) as mngr: urlopen(base_json_url, urlencode({'api_key': api_key})) # * He gets a 403 self.assertEquals(mngr.exception.code, 403) # * He tries using the right key. response = urlopen(base_json_url, urlencode({'api_key': 'some_new_api_key'})) # * It works. json_data = json.load(response) self.assertEquals(json_data['1']['1'], 5) # * He refreshes the sheet page self.refresh_sheet_page() # * and notes that his setting has been remembered by the server self.selenium.click('id=id_security_button') self.wait_for_element_visibility('id=id_security_form', True) self.wait_for_element_visibility('id=id_security_form_save_error', False) self.assertEquals(self.selenium.get_value('id=id_security_form_json_enabled_checkbox'), 'on') # * He makes the sheet private again. self.selenium.click('id=id_security_button') self.wait_for_element_visibility('id=id_security_form', True) self.wait_for_element_visibility('id=id_security_form_save_error', False) self.selenium.click('id=id_security_form_json_enabled_checkbox') self.selenium.click('id=id_security_form_ok_button') self.wait_for_element_visibility('id=id_security_form', False) # * He tries with the key that worked last time. with self.assertRaises(HTTPError) as mngr: urlopen(base_json_url, urlencode({'api_key': 'some_new_api_key'})) # * He gets a 403 self.assertEquals(mngr.exception.code, 403) def test_link_to_documentation_in_dialog(self): # * Harold logs in to Dirigible and creates a new sheet self.login_and_create_new_sheet() # * He goes to the security dialog. self.selenium.click('id=id_security_button') self.wait_for_element_visibility('id=id_security_form', True) self.wait_for_element_visibility('id=id_security_form_save_error', False) # * He is puzzled by the JSON API option, so he clicks on its help link # * The link goes into a new window ## Selenium gets Very Unhappy with target=_blank, so we just check that it's there and ## then remove it so as not to frighten the poor thing. self.assertEquals( self.selenium.get_attribute('id=id_security_form_json_help@target'), "_blank" ) self.selenium.get_eval("window.$('#id_security_form_json_help').removeAttr('target')") self.selenium.click("id=id_security_form_json_help") self.selenium.wait_for_page_to_load(PAGE_LOAD_TIMEOUT) # containing a documentation page that explains it all title = self.browser.title self.assertTrue(title.startswith('The JSON API')) self.assertTrue(title.endswith('documentation')) def test_run_worksheet_with_json_disabled_sheets(self): # * Harold logs in to Dirigible and creates a new sheet, with some stuff in it self.login_and_create_new_sheet() rws_sheet_url = self.browser.current_url self.enter_cell_text(1, 1, '5') # * He creates another new sheet self.create_new_sheet() base_json_url = urljoin(self.browser.current_url, 'v0.1/json/') # * and enables JSON API access self.selenium.click('id=id_security_button') self.wait_for_element_visibility('id=id_security_form', True) self.wait_for_element_visibility('id=id_security_form_save_error', False) self.selenium.click('id=id_security_form_json_enabled_checkbox') self.selenium.type('id=id_security_form_json_api_key', self.get_my_username()) self.selenium.click('id=id_security_form_ok_button') self.wait_for_element_visibility('id=id_security_form', False) # * He enters a formula that uses run_worksheet on the first sheet self.enter_cell_text(1, 1, "=run_worksheet('%s')[1, 1].value" % (rws_sheet_url,)) self.wait_for_cell_value(1, 1, '5') # * and tries to access it via the JSON API try: response = urlopen(base_json_url, urlencode({'api_key': self.get_my_username()})) except HTTPError, err: self.fail(err.read()) # * It works. json_data = json.load(response) self.assertEquals(json_data['1']['1'], 5) def test_json_api_auth_reports_server_error(self): # * Harold logs in to Dirigible and creates a new sheet, with some stuff in it self.login_and_create_new_sheet() # * and enables JSON API access self.selenium.click('id=id_security_button') self.wait_for_element_visibility('id=id_security_form', True) self.wait_for_element_visibility('id=id_security_form_save_error', False) self.selenium.click('id=id_security_form_json_enabled_checkbox') self.selenium.type('id=id_security_form_json_api_key', self.get_my_username()) # Something goes wrong with the Dirigible server old_set_security_settings_url = self.selenium.get_eval("window.urls.setSecuritySettings") self.selenium.get_eval("window.urls.setSecuritySettings = 'blergh'") # Blissfully unaware of this, Harold clicks OK. self.selenium.click('id=id_security_form_ok_button') # The dialog remains, and an error div appears to tell him that something is wrong. self.wait_for_element_visibility('id=id_security_form', True) self.wait_for_element_visibility('id=id_security_form_save_error', True) # He waits for a moment, and the server magically fixes itself. self.selenium.get_eval("window.urls.setSecuritySettings = '%s'" % (old_set_security_settings_url,)) # He tries again, and the dialog disappears. self.selenium.click('id=id_security_form_ok_button') self.wait_for_element_visibility('id=id_security_form', False) # The sheet page confirms that his changes are there. self.assertTrue( 'JSON API enabled' in self.selenium.get_attribute('css=#id_security_button@title') ) self.assertTrue( 'JSON API enabled' in self.selenium.get_attribute('css=#id_security_button@alt') ) # He pops up the dialog and is pleased to see that there is no error message there. self.selenium.click('id=id_security_button') self.wait_for_element_visibility('id=id_security_form', True) self.wait_for_element_visibility('id=id_security_form_save_error', False) # He refreshes the page and confirms from the toolbar buttons that his changes really were saved. self.refresh_sheet_page() self.assertTrue( 'JSON API enabled' in self.selenium.get_attribute('css=#id_security_button@title') ) self.assertTrue( 'JSON API enabled' in self.selenium.get_attribute('css=#id_security_button@alt') ) ================================================ FILE: dirigible/fts/tests/test_2571_DocumentationAndBlogLinks.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from urlparse import urljoin from functionaltest import FunctionalTest, Url class Test_2571_Documentation(FunctionalTest): def get_link_href(self, link_id): url = self.selenium.get_attribute("id=%s@href" % (link_id,)) return urljoin(Url.ROOT, url) def test_main_documentation_page_exists(self): # * Harold goes to the Dirigible front page. self.go_to_url(Url.ROOT) # * He sees that there is a link to a blog, and notes down where it goes to. blog_url = self.get_link_href("id_blog_link") # * He goes to url http://IP/documentation self.go_to_url(Url.DOCUMENTATION) # * He gets back a page, not a 404, the title of which which contains the words 'Dirigible' and 'documentation' title = self.browser.title.lower() self.assertTrue("documentation" in title) self.assertTrue("dirigible" in title) # * He logs in and creates a sheet, and notices that there is a link to the # same documentation page. self.login_and_create_new_sheet() self.assertEquals(self.get_link_href("id_help_link"), Url.DOCUMENTATION) # ...and that there is also a link to the same blog. self.assertEquals(self.get_link_href("id_blog_link"), blog_url) # * He follows the link to his dashboard. self.click_link('id_account_link') # * He sees the same help and blog links there, and confirms they go to the same places. self.assertEquals(self.get_link_href("id_help_link"), Url.DOCUMENTATION) self.assertEquals(self.get_link_href("id_blog_link"), blog_url) ================================================ FILE: dirigible/fts/tests/test_2577_SaveColumnWidths.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from textwrap import dedent import time from functionaltest import FunctionalTest, PAGE_LOAD_TIMEOUT DELTA = 30 class Test_2537_save_column_widths(FunctionalTest): def get_column_header_locator(self, column_name): return 'css=div[title=%s]' % (column_name,) def get_column_resize_handle_locator(self, column_name): return "%s div.slick-resizable-handle" % (self.get_column_header_locator(column_name),) def get_column_width(self, column_name): return self.selenium.get_element_width(self.get_column_header_locator(column_name)) def wait_for_column_width(self, column_name, width): self.wait_for( lambda: self.get_column_width(column_name) == width, lambda: 'column %s width to become %s (was %s)' % (column_name, width, self.get_column_width(column_name)) ) def resize_column(self, column_name, width_delta): orig_column_width = self.get_column_width(column_name) self.selenium.drag_and_drop( self.get_column_resize_handle_locator(column_name), '%d,+0' % (width_delta,) ) self.wait_for_column_width(column_name, orig_column_width + width_delta) def test_save_em(self): # * Harold logs in and creates a new sheet self.login_and_create_new_sheet() # He resizes a column self.resize_column('B', 30) new_size = self.get_column_width('B') # * He refreshes the page self.refresh_sheet_page() # * the resized column is still enbiggenened self.wait_for_grid_to_appear() self.wait_for_column_width('B', new_size) def test_changing_column_widths_does_not_interrupt_recalc_but_does_save_widths(self): # * Harold logs in and creates a new sheet self.login_and_create_new_sheet() # * He sets up a long recalc self.enter_cell_text(1, 1, '1') self.append_usercode(dedent(''' worksheet[1, 1].value += 1 import time time.sleep(30) ''')) # * After it's been running for a while, but before it is finished, # he plays with the column widths. (Waiting for this makes sure that # we can distinguish between the completion of the first recalc (which # should happen within the time the usercode takes to execute plus or # minus a bit) and the completion of a second recalc that was somehow # triggered by broken column-resize code; resizing columns should # *not* trigger a recalc.) time.sleep(20) self.resize_column('B', 30) col_b_width = self.get_column_width('B') self.resize_column('C', -10) col_c_width = self.get_column_width('C') # * and notes that the recalc completes normally -- ie. # that when it completed it did not see the updated # column widths and think that the sheet had changed # underneath it, and thus abort and send back an error # -- and also that it completes within # from the initial setting of the usercode, not within # a larger amount of time (which would imply that setting # column widths triggered a recalc. self.wait_for_cell_value(1, 1, '2', timeout_seconds=15) # * He then refreshes the page to make sure that the recalc # that just finished copied the new column widths to the DB. self.refresh_sheet_page() self.wait_for_grid_to_appear() self.wait_for_column_width('B', col_b_width) self.wait_for_column_width('C', col_c_width) ================================================ FILE: dirigible/fts/tests/test_2581_FormulaBar.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from functionaltest import FunctionalTest, snapshot_on_error import key_codes class Test_2581_FormulaBar(FunctionalTest): def wait_for_formula_bar_enabled(self): self.wait_for( self.is_formula_bar_enabled, lambda: 'formula bar to enable', ) def wait_for_formula_bar_disabled(self): self.wait_for( lambda: not self.is_formula_bar_enabled(), lambda: 'formula bar to disable', ) def test_formula_bar(self): # Harold lacks the mental egg-juggling capacity of a Real Programmer, # and wants to be reminded of relevant state as he examines his # spreadsheet. # * He logs in to Dirigible and creates a new sheet self.login_and_create_new_sheet() # * He observes that there is a single-line text box above the grid. bar_bounds = self.get_element_bounds(self.get_formula_bar_locator()) grid_locator = "id=id_grid" grid_bounds = self.get_element_bounds(grid_locator) self.assertTrue( abs(bar_bounds.left - grid_bounds.left) < 50, "Grid and formula bar not roughly horizontally aligned" ) self.assertTrue( abs(bar_bounds.width - grid_bounds.width) < 100, "Grid and formula bar not roughly the same width" ) self.assertTrue( bar_bounds.top + bar_bounds.height < grid_bounds.top, "Formula bar not above grid" ) # * He notes that cell A1 is active, and he can edit the formula bar, # which is empty ## NB click doesn't work here -- it's for links and buttons only. ## Which makes it strange that it works with cells! self.wait_for_cell_to_become_active(1, 1) self.wait_for_formula_bar_enabled() self.click_formula_bar() self.assertTrue( self.is_element_focused('id=%s' % (self.get_formula_bar_id(),))) self.wait_for_formula_bar_contents("") # * As he starts to type "456", he observes that the cell editor # updates with each keystroke. self.human_key_press(key_codes.NUMBER_4) # workaround for defect T2708 #self.wait_for_cell_editor_content("4") self.human_key_press(key_codes.NUMBER_5) self.wait_for_cell_editor_content("45") self.human_key_press(key_codes.NUMBER_6) self.wait_for_cell_editor_content("456") # * He then edits cell A2 self.open_cell_for_editing(1, 2) self.wait_for_cell_to_enter_edit_mode(1, 2) # and notes that the formula bar becomes empty self.wait_for_formula_bar_contents("") # while cell A1 still contains "456". self.wait_for_cell_value(1, 1, "456") # * He types "123"; as he does so, the contents of the formula bar # update with each keystroke. self.human_key_press(key_codes.NUMBER_1) self.wait_for_formula_bar_contents("1") self.human_key_press(key_codes.NUMBER_2) self.wait_for_formula_bar_contents("12") self.human_key_press(key_codes.NUMBER_3) self.wait_for_formula_bar_contents("123") # * He edits cell A1, and notes that both the cell and the formula bar # read "456". self.open_cell_for_editing(1, 1) self.wait_for_cell_editor_content("456") self.wait_for_formula_bar_contents("456") # * Harold edits A3, and enters "78". self.open_cell_for_editing(1, 3) self.human_key_press(key_codes.NUMBER_7) self.human_key_press(key_codes.NUMBER_8) # The formula bar shows 78. self.wait_for_formula_bar_contents("78") # * He clicks in the formula bar, hits the "end" key, and enters "90". self.click_formula_bar() self.human_key_press(key_codes.END) self.human_key_press(key_codes.NUMBER_9) self.human_key_press(key_codes.NUMBER_0) # The cell editor and the formula bar both show 7890. self.wait_for_cell_editor_content("7890") self.assert_formula_bar_contains("7890") # * Harold clicks on A4, self.click_on_cell(1, 4) # and then wonders whether he can commit changes from the formula bar # without click to a cell, so he clicks on the formula bar, self.click_formula_bar() # types "321". self.human_key_press(key_codes.NUMBER_3) self.human_key_press(key_codes.NUMBER_2) self.human_key_press(key_codes.NUMBER_1) self.human_key_press(key_codes.ENTER) # and is delighted to see the cell update. self.wait_for_cell_value(1, 4, "321") @snapshot_on_error def test_formula_bar_remains_enabled_at_all_times(self): # Harry logs in and creates a new sheet. self.login_and_create_new_sheet() # * the formula bar is enabled because a cell has focus self.wait_for_formula_bar_enabled() self.wait_for_cell_to_become_active(1, 1) # * he clicks in the output pane # the formula bar remains enabled self.selenium.click('id=id_console') self.wait_for_formula_bar_enabled() # * he clicks in the usercode # the formula bar remains enabled self.selenium.get_eval('window.editor.focus()') self.wait_for_formula_bar_enabled() # * he clicks in the sheet title # the formula bar remains enabled self.selenium.click('id=id_sheet_name') self.wait_for_formula_bar_enabled() # He presses escape to cancel the sheet title edit self.human_key_press(key_codes.ESCAPE) # * He clicks in the formula bar self.click_formula_bar() # and types "321". self.human_key_press(key_codes.NUMBER_3) self.human_key_press(key_codes.NUMBER_2) self.human_key_press(key_codes.NUMBER_1) self.human_key_press(key_codes.ENTER) # and is delighted to see cell A1 update. self.wait_for_cell_value(1, 1, "321") ================================================ FILE: dirigible/fts/tests/test_2582_ReferencingEmptyCell.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from functionaltest import FunctionalTest class Test_2582_ReferencingEmptyCell(FunctionalTest): def test_referencing_empty_cell(self): # Harold logs in and creates a new sheet. self.login_and_create_new_sheet() # He enters "=A1" into B1 self.enter_cell_text(2, 1, '=A1') # He sees that B1 is in its "I have no value but my formula is '=A1'" state. self.wait_for_cell_shown_formula(2, 1, '=A1') # Cell A1 is blank. self.wait_for_cell_value(1, 1, '') self.wait_for_spinner_to_stop() # There is nothing in the error console. self.assertTrue( self.get_console_content().startswith('Took'), "Unexpected error console content:\n%r" % (self.get_console_content(),) ) # He adds code to set the value of C1 to the end of the user code. self.append_usercode("worksheet[3, 1].value = 23") # He notes that it is being executed. self.wait_for_cell_value(3, 1, '23') ================================================ FILE: dirigible/fts/tests/test_2592_Cut_Copy_Paste_Within_Dirigible.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from __future__ import with_statement from textwrap import dedent from functionaltest import FunctionalTest, Url import key_codes class Test_2592_Cut_Copy_Paste_within_Dirigible(FunctionalTest): def wait_for_only_active_cell_selected(self): def is_only_active_cell_selected(): return self.selenium.get_eval(""" (function() { var selectedCells = window.$('.selected'); if (selectedCells.length !== 1) { return false; } return selectedCells[0] === window.$('.active')[0]; })() """) == 'true' self.wait_for( is_only_active_cell_selected, lambda: "Only active cell to be selected" ) def test_block_selection(self): # * Harold logs in to Dirigible and creates a new sheet self.login_and_create_new_sheet() # He tries to select a range by dragging from B2 to C5 self.mouse_drag((2, 3), (3, 6)) self.assert_current_selection((2, 3), (3, 6)) # he clicks outside the range and sees his selection disappear self.click_on_cell(7, 7) self.wait_for_only_active_cell_selected() # He then uses shift + arrow keys # selenium needs a little jquery-kick to get shift-key-down working self.selenium.get_eval("window.$('div.grid-canvas').trigger({type:'keydown', which: 16});") self.human_key_press(key_codes.LEFT) self.assert_current_selection((6, 7), (7, 7)) self.human_key_press(key_codes.LEFT) self.assert_current_selection((5, 7), (7, 7)) self.human_key_press(key_codes.LEFT) self.assert_current_selection((4, 7), (7, 7)) self.human_key_press(key_codes.UP) self.assert_current_selection((4, 6), (7, 7)) self.human_key_press(key_codes.UP) self.assert_current_selection((4, 5), (7, 7)) self.human_key_press(key_codes.UP) self.assert_current_selection((4, 4), (7, 7)) self.selenium.get_eval("window.$('div.grid-canvas').trigger({type:'keyup', which: 16});") # he clicks *inside* the range and sees his selection disappear self.click_on_cell(5, 5) self.wait_for_only_active_cell_selected() # * He finally settles on shift + mouse click as his favourite # method for selecting a range self.click_on_cell(2, 3) with self.key_down(key_codes.SHIFT): self.click_on_cell(8, 7) self.assert_current_selection((2, 3), (8, 7)) # he checks you cannot select the header areas self.click_on_cell(1, 1) self.selenium.get_eval("window.$('div.grid-canvas').trigger({type:'keydown', which: 16});") self.human_key_press(key_codes.LEFT) self.assert_current_selection((1, 1), (1, 1)) self.human_key_press(key_codes.UP) self.assert_current_selection((1, 1), (1, 1)) def assert_copy_and_paste( self, operation, source_sheet, dest_sheet=None, dest_location=None, to_set='formula' ): if dest_sheet is None: dest_sheet = source_sheet # he populates some cells using usercode (because the test runs faster) orig_usercode = self.get_usercode() self.prepend_usercode(dedent(''' for row in range(3, 6): for col in 'BC': worksheet[col, row].%s = '%%s%%d' %% (col, row) ''' % (to_set,) )) self.wait_for_cell_value(2, 3, 'B3') if to_set == 'formula': self.enter_usercode(orig_usercode) self.wait_for_spinner_to_stop() # he copies (or cuts) a region from one place operation((2, 3), (3, 5)) # If he's cutting, it disappears if operation == self.cut_range: self.wait_for_cell_value(2, 3, '') self.wait_for_cell_value(3, 3, '') self.wait_for_cell_value(2, 4, '') self.wait_for_cell_value(3, 4, '') self.wait_for_cell_value(2, 5, '') self.wait_for_cell_value(3, 5, '') # ...and pastes it elsewhere if dest_sheet != source_sheet: self.go_to_url( Url.sheet_page(self.get_my_username(), dest_sheet) ) self.wait_for_grid_to_appear() self.paste_range(dest_location) # the destination is populated c, r = dest_location self.wait_for_cell_value( c, r, 'B3') self.wait_for_cell_value(1+c, r, 'C3') self.wait_for_cell_value( c, 1+r, 'B4') self.wait_for_cell_value(1+c, 1+r, 'C4') self.wait_for_cell_value( c, 2+r, 'B5') self.wait_for_cell_value(1+c, 2+r, 'C5') def test_copy_and_paste_southeast(self): # * Harold logs in to Dirigible and creates a new sheet sheet_id = self.login_and_create_new_sheet() # he does a copy and paste, and sees the destination cells update self.assert_copy_and_paste(self.copy_range, sheet_id, dest_location=(3, 4)) # the non-overlapped source cells are unchanged self.wait_for_cell_value(2, 3, 'B3') self.wait_for_cell_value(2, 4, 'B4') self.wait_for_cell_value(2, 5, 'B5') self.wait_for_cell_value(3, 3, 'C3') def test_copy_and_paste_northwest(self): # * Harold logs in to Dirigible and creates a new sheet sheet_id = self.login_and_create_new_sheet() self.assert_copy_and_paste(self.copy_range, sheet_id, dest_location=(1, 2)) # the non-overlapped source cells are unchanged self.wait_for_cell_value(2, 5, 'B5') self.wait_for_cell_value(3, 3, 'C3') self.wait_for_cell_value(3, 4, 'C4') self.wait_for_cell_value(3, 5, 'C5') def test_copy_and_paste_to_new_sheet(self): # * Harold logs in to Dirigible and creates a new sheet dest_sheet = self.login_and_create_new_sheet() source_sheet = self.create_new_sheet() self.assert_copy_and_paste( self.copy_range, source_sheet, dest_sheet, dest_location=(3, 4)) # the cells in the original sheet are all still there self.go_to_url( Url.sheet_page(self.get_my_username(), source_sheet) ) self.wait_for_grid_to_appear() self.wait_for_cell_value(2, 3, 'B3') self.wait_for_cell_value(2, 4, 'B4') self.wait_for_cell_value(2, 5, 'B5') self.wait_for_cell_value(3, 3, 'C3') self.wait_for_cell_value(3, 4, 'C4') self.wait_for_cell_value(3, 5, 'C5') def test_cut_and_paste_southeast(self): # * Harold logs in to Dirigible and creates a new sheet sheet_id = self.login_and_create_new_sheet() self.assert_copy_and_paste(self.cut_range, sheet_id, dest_location=(3, 4)) # the source cells that weren't pasted over are cleared self.wait_for_cell_value(2, 3, '') self.wait_for_cell_value(3, 3, '') self.wait_for_cell_value(2, 4, '') self.wait_for_cell_value(2, 5, '') def test_cut_and_paste_northwest(self): # * Harold logs in to Dirigible and creates a new sheet sheet_id = self.login_and_create_new_sheet() self.assert_copy_and_paste(self.cut_range, sheet_id, dest_location=(1, 2)) # the source cells that weren't pasted over are cleared self.wait_for_cell_value(2, 5, '') self.wait_for_cell_value(3, 3, '') self.wait_for_cell_value(3, 4, '') self.wait_for_cell_value(3, 5, '') def test_cut_and_paste_to_new_sheet(self): # * Harold logs in to Dirigible and creates two new sheets dest_sheet = self.login_and_create_new_sheet() source_sheet = self.create_new_sheet() # He cuts stuff from one to the other self.assert_copy_and_paste( self.cut_range, source_sheet, dest_sheet, dest_location=(3, 4)) def test_multiple_pastes_after_copy(self): # * Harold logs in to Dirigible and creates a new sheet sheet_id = self.login_and_create_new_sheet() # He copies some stuff towards the southeast self.assert_copy_and_paste(self.copy_range, sheet_id, dest_location=(3, 4)) # the non-overlapped source cells are unchanged self.wait_for_cell_value(2, 3, 'B3') self.wait_for_cell_value(2, 4, 'B4') self.wait_for_cell_value(2, 5, 'B5') self.wait_for_cell_value(3, 3, 'C3') # he then tries pasting again, even further south east. c, r = 10, 20 self.paste_range((c, r)) # the destination is populated self.wait_for_cell_value( c, r, 'B3') self.wait_for_cell_value(1+c, r, 'C3') self.wait_for_cell_value( c, 1+r, 'B4') self.wait_for_cell_value(1+c, 1+r, 'C4') self.wait_for_cell_value( c, 2+r, 'B5') self.wait_for_cell_value(1+c, 2+r, 'C5') # the source and original paste are unchanged self.wait_for_cell_value(2, 3, 'B3') self.wait_for_cell_value(3, 4, 'B3') # he sets a cell formula self.enter_cell_text(1, 1, "hope this doesn't kill the clipboard!") self.wait_for_cell_value(1, 1, "hope this doesn't kill the clipboard!") # he tries to paste again slightly southeast c, r = 11, 21 self.paste_range((c, r)) # and is pleased to see the destination populated self.wait_for_cell_value( c, r, 'B3') self.wait_for_cell_value(1+c, r, 'C3') self.wait_for_cell_value( c, 1+r, 'B4') self.wait_for_cell_value(1+c, 1+r, 'C4') self.wait_for_cell_value( c, 2+r, 'B5') self.wait_for_cell_value(1+c, 2+r, 'C5') # he double checks the source and earlier pastes are unchanged self.wait_for_cell_value(2, 3, 'B3') self.wait_for_cell_value(3, 4, 'B3') self.wait_for_cell_value(10, 20, 'B3') def test_multiple_pastes_after_cut(self): # * Harold logs in to Dirigible and creates a new sheet sheet_id = self.login_and_create_new_sheet() # He cuts & pastes some stuff towards the southeast self.assert_copy_and_paste(self.cut_range, sheet_id, dest_location=(3, 4)) # he hits paste again, further down c, r = 10, 20 self.paste_range((c, r)) # the destination is populated self.wait_for_cell_value( c, r, 'B3') self.wait_for_cell_value(1+c, r, 'C3') self.wait_for_cell_value( c, 1+r, 'B4') self.wait_for_cell_value(1+c, 1+r, 'C4') self.wait_for_cell_value( c, 2+r, 'B5') self.wait_for_cell_value(1+c, 2+r, 'C5') # he now sets a cell formula self.enter_cell_text(1, 1, "hope this doesn't kill the clipboard!") self.wait_for_cell_value(1, 1, "hope this doesn't kill the clipboard!") # he tries to paste again slightly southeast c, r = 11, 21 self.paste_range((c, r)) # and is pleased to see the destination populated self.wait_for_cell_value( c, r, 'B3') self.wait_for_cell_value(1+c, r, 'C3') self.wait_for_cell_value( c, 1+r, 'B4') self.wait_for_cell_value(1+c, 1+r, 'C4') self.wait_for_cell_value( c, 2+r, 'B5') self.wait_for_cell_value(1+c, 2+r, 'C5') # he double checks the original source is still empty self.wait_for_cell_value(2, 3, '') #and earlier pastes are unchanged self.wait_for_cell_value(3, 4, 'B3') self.wait_for_cell_value(10, 20, 'B3') def test_values_wo_formulae_are_promoted(self): # * Harold logs in to Dirigible and creates a new sheet sheet_id = self.login_and_create_new_sheet() # he sets up a range with just values, no formulae, and checks they get # copied self.assert_copy_and_paste(self.copy_range, sheet_id, dest_location=(3, 4), to_set='value') def test_target_range_is_cleared_before_paste(self): # * Harold logs in to Dirigible and creates a new sheet self.login_and_create_new_sheet() # * He enters some values into the grid # making sure that there are some gaps for row in range(1, 6, 2): self.enter_cell_text(1, row, 'filled') self.copy_range((1, 1), (1, 6)) # * He copies a section of the values over another section # and notes that the gaps in the source range are replicated in the target self.paste_range((1, 2)) self.wait_for_cell_value(1, 3, '') def test_mouse_drags_dont_lose_current_cell_edits(self): # * Harold logs in to Dirigible and creates a new sheet self.login_and_create_new_sheet() self.click_on_cell(1, 1) self.human_key_press(key_codes.NUMBER_1) self.human_key_press(key_codes.NUMBER_2) self.mouse_drag((2, 3), (3, 6)) ================================================ FILE: dirigible/fts/tests/test_2595_Spinner.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from functionaltest import FunctionalTest class Test_2595_Throbber(FunctionalTest): def test_spinner_appears_during_recalcs(self): # * Harold likes to know when dirigible is working hard on his calculations # * He logs in and creates a new sheet self.login_and_create_new_sheet() # * When the grid has appeared, the spinner might be visible, but it disappears # rapidly as the initial empty recalc completes. self.wait_for_spinner_to_stop() # * and enters some hard-working user-code self.append_usercode('import time\ntime.sleep(20)\nworksheet[1,1].value="ready"') # * He spots the spinner on the page self.wait_for(self.is_spinner_visible, lambda : 'spinner not present', timeout_seconds = 5) # * When the recalc is done, he sees the spinner go away self.wait_for_cell_value(1, 1, 'ready', timeout_seconds=25) self.assertTrue(self.is_element_present('css=#id_spinner_image.hidden')) ================================================ FILE: dirigible/fts/tests/test_2597_CapRecalcTime.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from functionaltest import FunctionalTest class Test_2597_CapRecalcTime(FunctionalTest): def test_recalcs_get_stopped(self): # * Harold doesn't want to waste money on recalculations where he created an # infinite loop. Dirigible helpfully kills any recalculations that are # taking too long. The default (set in the database per sheet) is 60sec. # * He logs in and creates a new sheet self.login_and_create_new_sheet() # * and enters some ill-advised user-code self.append_usercode('while True: pass\n\n#some stuff') fn_call_line = self.get_usercode().split('\n').index('while True: pass') # * He notes that after a minute, there is a message in the console # telling him that his recalculation timed out. self.wait_for_console_content( 'TimeoutException: \n User code line 10', timeout_seconds=59 ) ================================================ FILE: dirigible/fts/tests/test_2601_UndefinedShouldBeAvailableToUsercode.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from functionaltest import FunctionalTest class Test_2601_UndefinedShouldBeAvailableToUsercode(FunctionalTest): def test_use_undefined(self): # * Harold logs in to Dirigible and creates a new sheet self.login_and_create_new_sheet() # * He uses undefined in A1. self.enter_cell_text(1, 1, '=A2==undefined') self.wait_for_cell_value(1, 1, 'True') self.assert_cell_has_no_error(1, 1) # * he uses undefined in usercode self.append_usercode('worksheet[1,3].value = worksheet[1,4].value==undefined') self.wait_for_cell_value(1, 3, 'True') self.assertTrue(self.get_console_content().startswith('Took')) # hooray! ================================================ FILE: dirigible/fts/tests/test_2602_SheetPageShouldDisplayBeforeFirstRecalcComplete.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from textwrap import dedent from functionaltest import FunctionalTest, Url class Test_2602_SheetPageShouldDisplayBeforeFirstRecalcComplete(FunctionalTest): def test_sheet_page_displays_fast(self): # * Harold logs in to Dirigible and creates a new sheet self.login_and_create_new_sheet() sheet_page = self.browser.current_url # * He sets it up so that it takes 30 seconds to recalc. self.enter_cell_text(1, 2, '=123') self.enter_usercode(dedent(""" import time time.sleep(30.0) load_constants(worksheet) evaluate_formulae(worksheet) worksheet[2,2].value = 'usercode completed' """)) self.wait_for_cell_value(2, 2, 'usercode completed', timeout_seconds=35) self.wait_for_cell_value(1, 2, '123') # * He navigates to a different page. self.go_to_url(Url.ROOT) # * He goes back to the sheet page. self.go_to_url(sheet_page) # * He notes that the grid takes less than 10 seconds to appear. self.wait_for_grid_to_appear(timeout_seconds=10) # * Once the grid is loaded, but before the recalc has completed, it # displays the results of his formulae and usercode-only values # even before the recalc is complete self.wait_for_cell_value(1, 2, '123', timeout_seconds=35) self.wait_for_cell_value(2, 2, 'usercode completed') ================================================ FILE: dirigible/fts/tests/test_2603_WorksheetsMayOnlyContainCells.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from textwrap import dedent from functionaltest import FunctionalTest class test_2603_WorksheetsMayOnlyContainCells(FunctionalTest): def test_setting_worksheet_location_to_non_cell_raises(self): # * Harold logs in to Dirigible and creates a new sheet self.login_and_create_new_sheet() # * He enters usercode which sets a worksheet location to a non-cell self.append_usercode('worksheet[1,1] = 123') error_line = len(self.get_usercode().split('\n')) expected = dedent(''' TypeError: Worksheet locations must be Cell objects User code line %d''' % (error_line,))[1:] self.wait_for_console_content(expected) # hooray! ================================================ FILE: dirigible/fts/tests/test_2616_RootPageIsDashboard.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from urlparse import urlparse, urlunparse from functionaltest import FunctionalTest class Test_2616_RootPageIsDashboard(FunctionalTest): # The bulk of this was tested by changing the existing functional tests to reflect the new # behaviour; we just have new stuff in this file. def test_old_url_redirects(self): # Harold has some old bookmarks to pages that have moved as part of this story. # He is delighted to find they still work. self.assert_redirects('/static/dirigible/about.html', '/about/') self.assert_redirects('/static/dirigible/contact.html', '/contact/') self.assert_redirects('/static/dirigible/pricing.html', '/pricing/') self.assert_redirects('/static/dirigible/privacy.html', '/privacy/') self.assert_redirects('/static/dirigible/terms.html', '/terms/') self.assert_redirects('/static/dirigible/video.html', '/video/') ================================================ FILE: dirigible/fts/tests/test_2621_CanSaveSheetsWithLotsOfFormulae.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from textwrap import dedent from functionaltest import FunctionalTest, snapshot_on_error class Test_2621_CanSaveSheetsWithLotsOfFormulae(FunctionalTest): @snapshot_on_error def test_can_save_lots_of_formulae(self): # * Harold logs in to Dirigible and creates a new sheet self.login_and_create_new_sheet() sheet_page = self.browser.current_url # * Typing tirelessly, he enters moderately-sized formulae in # 23 columns across 400 rows cell_value = "a" * 5 self.enter_usercode(dedent(""" for col in range(1, 45): for row in range(1, 401): worksheet[col, row].formula = %r """ % cell_value)) self.wait_for_cell_shown_formula(1, 1, cell_value, timeout_seconds=30) self.enter_usercode(dedent(""" load_constants(worksheet) evaluate_formulae(worksheet) """)) # * He looks at the grid and sees that the data appear to be # there self.wait_for_cell_value(1, 1, cell_value, timeout_seconds=30) self.wait_for_cell_value(1, 10, cell_value) self.wait_for_cell_value(10, 1, cell_value) self.wait_for_cell_value(10, 10, cell_value) self.wait_for_cell_value(5, 5, cell_value) # * He goes back to his dashboard. self.go_to_url(sheet_page) # * He returns to the grid, and is pleased to see that the data # are still there. self.wait_for_cell_value(1, 1, cell_value, timeout_seconds=30) self.wait_for_cell_value(1, 10, cell_value) self.wait_for_cell_value(10, 1, cell_value) self.wait_for_cell_value(10, 10, cell_value) self.wait_for_cell_value(5, 5, cell_value) ================================================ FILE: dirigible/fts/tests/test_2622_CellRanges.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from functionaltest import FunctionalTest, Url from textwrap import dedent class Test_2622_CellRanges(FunctionalTest): def test_can_use_cell_ranges_in_usercode(self): # * Harold logs in to Dirigible and creates a new sheet self.login_and_create_new_sheet() # * While perusing the documentation, he notices a reference to cell ranges. sheet_url = self.browser.current_url self.go_to_url(Url.API_DOCS) self.wait_for_element_to_appear("id=CellRange") self.wait_for_element_to_appear("id=Worksheet.cell_range") # * Fascinated by this leap forward in the API, he creates a cellrange object in usercode self.go_to_url(sheet_url) self.wait_for_grid_to_appear() self.append_usercode(dedent(''' worksheet.B3.value = 1 worksheet.C3.value = 2 worksheet.D3.value = 'outside' worksheet.B4.value = 3 #worksheet C4 is blank worksheet.B5.value = 5 worksheet.C5.value = 6 #worksheet row 6 is blank worksheet.B7.value = 'a random string in the middle' worksheet.C7.value = 10 my_range_no_blanks = worksheet.cell_range('B3:B5') cells_total = sum(my_range_no_blanks) worksheet.A1.value = cells_total my_full_range_syntax1 = worksheet.cell_range('B3','C10') for col, value in enumerate(my_full_range_syntax1): worksheet[col+2, 1].value = value my_full_range_syntax2 = worksheet.cell_range((2,3),'C10') for col, cell in enumerate(my_full_range_syntax2.cells): worksheet[col+2, 2].value = cell.value ''')) self.wait_for_cell_value(1, 1, '9') data1 = [1,2,3,'',5,6,'','','a random string in the middle',10] for col, value in enumerate(data1): self.wait_for_cell_value(col+2, 1, str(value)) for col, value in enumerate(data1): self.wait_for_cell_value(col+2, 2, str(value)) def test_can_use_cell_ranges_in_formulae(self): # * Harold logs in to Dirigible and creates a new sheet self.login_and_create_new_sheet() self.enter_cell_text(1, 1, '1') self.enter_cell_text(1, 2, '2') self.enter_cell_text(1, 3, '3') self.enter_cell_text(2, 1, '=sum(A1:A3)') self.wait_for_cell_value(2, 1, '6') def test_bare_cell_ranges_in_cells_dont_cause_recursive_explosion(self): # * Harold logs in to Dirigible and creates a new sheet self.login_and_create_new_sheet() self.enter_cell_text(1, 1, '1') self.enter_cell_text(1, 2, '2') self.enter_cell_text(1, 3, '3') self.enter_cell_text(2, 1, '=A1:A3') self.wait_for_cell_value(2, 1, ">") def test_cellrange_dependencies(self): # * Harold logs in to Dirigible and creates a new sheet self.login_and_create_new_sheet() # * he creates many cell formulae self.prepend_usercode(dedent(''' for col in range(2, 12): for row in range(2, 12): worksheet[col, row].formula = '=1' worksheet[1, 1].formula = '=sum(b2:k11)' ''')) # * the sum in a1 ought to be calculated after all # the other cell formulae self.wait_for_cell_value(1, 1, '100') ================================================ FILE: dirigible/fts/tests/test_2631_BlogRedirect.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # try: import unittest2 as unittest except ImportError: import unittest from urllib2 import urlopen from urlparse import urljoin from functionaltest import FunctionalTest, PAGE_LOAD_TIMEOUT, Url # This test is a bit of a pain. Ideally we'd visit the blog URLs in Selenium and # check that get_location() returned the correct redirected-to URLs. The problem # is that when Selenium is driving Chrome, and it visits a page on our tested # domain that redirects to another domain, Chrome blows up because it suspects # some kind of cross-site scripting attack. This is a specific instance of the # more general problem that a Selenium test driving Chrome can access one and only # one domain. # # As a result, we can't use Selenium, so this test doesn't actually run in a # browser. class Test_2631_BlogRedirect(unittest.TestCase): def test_blog_page_redirect(self): # Harold has a bookmark to our blog, but it dates from a long time ago when # it was hosted at projectdirigible.com/blog. He visits it. old_blog_url = urljoin(Url.ROOT, '/blog/') opened_page = urlopen(old_blog_url) # He finds himself at blog.projectdirigible.com. self.assertEquals(opened_page.geturl(), "http://blog.projectdirigible.com/") # He goes to a specific post that he bookmarked because he found it interesting opened_page = urlopen(urljoin(old_blog_url, "?p=196")) # He finds himself on the equivalent page on the new site. self.assertEquals(opened_page.geturl(), "http://blog.projectdirigible.com/?p=196") def test_blog_rss_redirect(self): # Harold subscribed to our blog's RSS feed a long time ago when it was hosted # at projectdirigible.com/blog. His RSS reader tries to access the feed. old_rss_url = urljoin(Url.ROOT, '/blog/?feed=rss2') opened_page = urlopen(old_rss_url) # It winds up getting it from blog.projectdirigible.com. self.assertEquals(opened_page.geturl(), "http://blog.projectdirigible.com/?feed=rss2") ================================================ FILE: dirigible/fts/tests/test_2633_CursorKeysMoveAroundGrid.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from __future__ import with_statement from functionaltest import FunctionalTest import key_codes class Test_2633_CursorKeysMoveAroundGrid(FunctionalTest): def get_cell_editor_cursor_position(self): try: selection_start = int(self.selenium.get_eval("window.$('%s').caret().start" % (self.get_cell_editor_css(),))) selection_end = int(self.selenium.get_eval("window.$('%s').caret().end" % (self.get_cell_editor_css(),))) except ValueError: return None self.assertEquals(selection_start, selection_end, "Time to refactor?") return selection_start def assert_editing_cell_at_position(self, col, row, position): self.assert_cell_is_current_and_is_editing(col, row) self.assertEquals(position, self.get_cell_editor_cursor_position()) def test_navigate_with_cursor_keys(self): # * Harold logs in to Dirigible and creates a new sheet self.login_and_create_new_sheet() self.selenium.get_eval('window.$(".grid-canvas").focus();') # * He clicks on a random cell self.click_on_cell(3, 4) # and it is clear to him that he is not in edit mode # ie focus is not captured by the input box self.assert_cell_is_current_but_not_editing(3, 4) # * He uses the LRUD cursor keys and sees he is navigating around # the grid self.human_key_press(key_codes.LEFT) self.assert_cell_is_current_but_not_editing(2, 4) self.human_key_press(key_codes.UP) self.assert_cell_is_current_but_not_editing(2, 3) self.human_key_press(key_codes.RIGHT) self.assert_cell_is_current_but_not_editing(3, 3) self.human_key_press(key_codes.DOWN) self.assert_cell_is_current_but_not_editing(3, 4) # * He finds that tab and shift-tab also move right and left self.human_key_press(key_codes.TAB) self.assert_cell_is_current_but_not_editing(4, 4) with self.key_down(key_codes.SHIFT): self.human_key_press(key_codes.TAB) self.assert_cell_is_current_but_not_editing(3, 4) # * He tries to go past the top row and gets blocked self.click_on_cell(5,1) self.human_key_press(key_codes.UP) self.assert_cell_is_current_but_not_editing(5, 1) # Same for leftmost column self.click_on_cell(1,5) self.human_key_press(key_codes.LEFT) self.assert_cell_is_current_but_not_editing(1, 5) # * He presses down to scroll off the bottom of the current screen # and notices the grid scrolls down with him self.click_on_cell(1,1) MAXROW = 50 for expected_row in range(1, MAXROW): self.assert_cell_is_current_but_not_editing(1, expected_row) self.assert_cell_visible(1, expected_row) self.human_key_press(key_codes.DOWN) # he sees it also works when he goes back up again for expected_row in range(MAXROW, 1, -1): self.assert_cell_is_current_but_not_editing(1, expected_row) self.assert_cell_visible(1, expected_row) self.human_key_press(key_codes.UP) # He goes to the edge of the screen on the right # and notices the grid scrolls right with him. self.click_on_cell(1,1) MAXCOL = 20 for expected_col in range(1, MAXCOL): self.assert_cell_is_current_but_not_editing(expected_col, 1) self.assert_cell_visible(expected_col, 1) self.human_key_press(key_codes.RIGHT) # he sees it also works when he goes back left again for expected_col in range(MAXCOL, 1, -1): self.assert_cell_is_current_but_not_editing(expected_col, 1) self.assert_cell_visible(expected_col, 1) self.human_key_press(key_codes.LEFT) def get_bottom_row(): # assumes the grid is scrolled up to row 1 to begin row = 1 while True: if not self.is_cell_visible(1, row): return row row += 1 # * He clicks in the middle of the visible grid, then # tries page up, but since the grid is already scrolled # all the way up, nothing happens self.click_on_cell(5, 5) self.human_key_press(key_codes.PAGE_UP) self.assertEquals(self.get_viewport_top(), 1) self.assert_cell_is_current_but_not_editing(5, 5) # * He tries pageDown and notes to his delight # that the grid scrolls down one page _, old_row = self.get_current_cell() old_distance_from_top = old_row - self.get_viewport_top() old_bottom_row = get_bottom_row() page_size = self.get_viewport_bottom() - self.get_viewport_top() - 1 self.human_key_press(key_codes.PAGE_DOWN) # correct new current cell is selected: self.assert_cell_is_current_but_not_editing( 5, old_row + page_size ) # grid is scrolled to correct place: _, new_row = self.get_current_cell() new_distance_from_top = new_row - self.get_viewport_top() self.assertEquals(new_distance_from_top, old_distance_from_top) # * He uses pageUp to return to the top of the grid _, row = self.get_current_cell() self.human_key_press(key_codes.PAGE_UP) self.assert_cell_is_current_but_not_editing(5, 5) # grid is scrolled to correct place: self.assertEquals(self.get_viewport_top(), 1) # * He selects a cell right at the bottom of the grid. self.click_on_cell(5, 990) # * He hits pageDown a bunch of times and discovers that he # reaches a stable last row pageDowns = 0 last_bottom_row = None hit_bottom = False while pageDowns < 6: current_bottom_row = self.get_viewport_bottom() if current_bottom_row == last_bottom_row: hit_bottom = True break last_bottom_row = current_bottom_row self.human_key_press(key_codes.PAGE_DOWN) pageDowns += 1 self.assertTrue(hit_bottom, "Didn't hit bottom after paging down") # * He hits pageUp and notes that the currently-selected cell # remains in the same place within the visible grid _, old_row = self.get_current_cell() old_distance_from_top = old_row - self.get_viewport_top() old_bottom_row = get_bottom_row() page_size = self.get_viewport_bottom() - self.get_viewport_top() - 1 self.human_key_press(key_codes.PAGE_UP) _, new_row = self.get_current_cell() new_distance_from_top = new_row - self.get_viewport_top() self.assertEquals(new_distance_from_top, old_distance_from_top) # * Amazed at the instinctiveness of Dirigible's grid navigation, # he wonders whether ctrl+home might take him back to the top left # and is well pleased when it does with self.key_down(key_codes.CTRL): self.human_key_press(key_codes.HOME) self.assert_cell_is_current_but_not_editing(1, 1) self.assert_cell_visible(1, 1) # * Ctrl+end takes him to the bottommost rightest cell also. with self.key_down(key_codes.CTRL): self.human_key_press(key_codes.END) self.assert_cell_is_current_but_not_editing(52, 1000) self.assert_cell_visible(52, 1000) def test_formula_bar_contents_should_follow_current_cell(self): # Harold logs in and creates a new sheet. self.login_and_create_new_sheet() # He enters some data. self.enter_cell_text(1, 1, "=4567") self.enter_cell_text(2, 2, "='B2'") # He moves around the grid using the cursor keys and the mouse and notes # that the contents of the formula bar always reflect the contents of the # selected cell. self.click_on_cell(1, 1) self.wait_for_formula_bar_contents("=4567") self.click_on_cell(1, 2) self.wait_for_formula_bar_contents("") self.click_on_cell(2, 2) self.wait_for_formula_bar_contents("='B2'") self.click_on_cell(2, 1) self.wait_for_formula_bar_contents("") self.human_key_press(key_codes.LEFT) self.wait_for_formula_bar_contents("=4567") self.human_key_press(key_codes.DOWN) self.wait_for_formula_bar_contents("") self.human_key_press(key_codes.RIGHT) self.wait_for_formula_bar_contents("='B2'") self.human_key_press(key_codes.UP) self.wait_for_formula_bar_contents("") # While an empty cell is selected, he clicks in the formula bar. self.click_on_cell(2, 1) self.wait_for_formula_bar_contents("") self.click_formula_bar() # He notes that the cell shifts into edit mode (though it remains empty) full_editor_locator = self.get_cell_editor_locator(2, 1) self.wait_for( lambda : self.is_element_present(full_editor_locator), lambda : "Editor at (%s, %s) to be present" % (2, 1), ) self.wait_for_cell_editor_content("") # He types into the formula bar and hits return. self.human_key_press(key_codes.NUMBER_7) # He sees his change applied. # (commented out as workaround for defect T2708) #self.wait_for_cell_value(2, 1, "7") self.human_key_press(key_codes.NUMBER_8) self.human_key_press(key_codes.ENTER) self.wait_for_cell_value(2, 1, "78") # He moves over to a populated cell and clicks in the formula bar self.click_on_cell(1, 1) self.wait_for_formula_bar_contents("=4567") self.click_formula_bar() # He notes that the cell shifts into edit mode and shows the formula. full_editor_locator = self.get_cell_editor_locator(1, 1) self.wait_for( lambda : self.is_element_present(full_editor_locator), lambda : "Editor at (%s, %s) to be present" % (1, 1), ) self.wait_for_cell_editor_content("=4567") # He changes the contents of the formula bar and hits ENTER. self.human_key_press(key_codes.NUMBER_9) self.human_key_press(key_codes.NUMBER_0) self.human_key_press(key_codes.ENTER) # He sees that change applied too. self.wait_for_cell_value(1, 1, "456790") def test_down_and_enter_keys_commit_edit_and_move_current_cell_down(self): # Harold logs in and creates a new sheet. self.login_and_create_new_sheet() # He enters some data into cell A1, and hits the down arrow. self.open_cell_for_editing(1, 1) self.human_key_press(key_codes.NUMBER_1) self.human_key_press(key_codes.NUMBER_2) self.human_key_press(key_codes.NUMBER_3) self.human_key_press(key_codes.DOWN) # His change is committed into A1 self.wait_for_cell_value(1, 1, "123") # A2 is the current cell. self.assert_cell_is_current_but_not_editing(1, 2) # He enters some data into cell B1, and hits the enter key. self.open_cell_for_editing(2, 1) self.human_key_press(key_codes.NUMBER_1) self.human_key_press(key_codes.NUMBER_2) self.human_key_press(key_codes.NUMBER_3) self.human_key_press(key_codes.ENTER) # His change is committed into B1 self.wait_for_cell_value(2, 1, "123") # B2 is the current cell. self.assert_cell_is_current_but_not_editing(2, 2) # He starts editing C1 in the formula bar and enters some data finishing with an enter. self.click_on_cell(3, 1) self.click_formula_bar() self.human_key_press(key_codes.NUMBER_1) self.human_key_press(key_codes.NUMBER_2) self.human_key_press(key_codes.NUMBER_3) self.human_key_press(key_codes.ENTER) # His change is committed into C1 self.wait_for_cell_value(3, 1, "123") # C2 is the current cell. self.assert_cell_is_current_but_not_editing(3, 2) # He starts editing D1 in the formula bar and enters some data finishing with an down arrow. self.click_on_cell(4, 1) self.click_formula_bar() self.human_key_press(key_codes.NUMBER_1) self.human_key_press(key_codes.NUMBER_2) self.human_key_press(key_codes.NUMBER_3) self.human_key_press(key_codes.DOWN) # His change is *not* committed, and the current cell remains D1. self.wait_for_cell_to_become_active(4, 1) self.assertTrue(self.is_element_focused(self.get_formula_bar_locator())) def test_begin_typing_enters_edit_mode(self): # * Harold logs in and creates a new sheet self.login_and_create_new_sheet() # * He clicks on a cell and begins typing self.click_on_cell(2, 3) self.human_key_press(key_codes.NUMBER_1) self.human_key_press(key_codes.NUMBER_2) self.human_key_press(key_codes.NUMBER_3) # * He is pleased to see that the cell he clicked on is in edit mode self.wait_for_cell_to_enter_edit_mode(2, 3) # .. and contains the stuff he typed self.wait_for_cell_editor_content('123') # .. as does the formula bar self.wait_for_formula_bar_contents('123') # * He enters some other data into a different cell, # clicks away from it and then back again self.enter_cell_text(4, 5, '="some other data"') self.click_on_cell(1, 1) self.click_on_cell(4, 5) # * He presses some random keys and notes that they don't cause # the cell to go into edit mode and that the original content # is unchanged self.human_key_press(key_codes.CTRL) self.assert_cell_is_current_but_not_editing(4, 5) self.human_key_press(key_codes.SHIFT) self.assert_cell_is_current_but_not_editing(4, 5) # * He clicks on the original cell and presses F2. self.click_on_cell(2, 3) # * He types some text and notes that it appears after the original text self.human_key_press(key_codes.F2) self.wait_for_cell_editor_content('123') self.human_key_press(key_codes.NUMBER_5) self.human_key_press(key_codes.NUMBER_1) self.wait_for_cell_editor_content('12351') # * He clicks on another cell with content and just starts typing. # * He notes that the previous content is replaced by what he just typed self.click_on_cell(4, 5) self.assert_cell_is_current_but_not_editing(4, 5) self.human_key_press(key_codes.NUMBER_5) self.wait_for_cell_to_enter_edit_mode(4, 5) self.wait_for_cell_editor_content('5') def test_cursor_keys_while_editing(self): # Harold logs in and creates a new sheet. self.login_and_create_new_sheet() # He enters some data into cell B2 test_cell = 2, 2 self.open_cell_for_editing(*test_cell) self.human_key_press(key_codes.NUMBER_1) self.human_key_press(key_codes.NUMBER_2) self.human_key_press(key_codes.NUMBER_3) # He hits the left arrow, and the cursor moves around within his entered data for expected_cursor_position in (2, 1, 0): self.human_key_press(key_codes.LEFT) self.assert_editing_cell_at_position(test_cell[0], test_cell[1], expected_cursor_position) # When he tries to move off the left-hand side of the input field, he is stopped. self.human_key_press(key_codes.LEFT) self.assert_editing_cell_at_position(test_cell[0], test_cell[1], 0) # He hits the right arrow, and the cursor moves around within his entered data for expected_cursor_position in (1, 2, 3): self.human_key_press(key_codes.RIGHT) self.assert_editing_cell_at_position(test_cell[0], test_cell[1], expected_cursor_position) # When he tries to move off the right-hand side of the input field, he is stopped. self.human_key_press(key_codes.RIGHT) self.assert_editing_cell_at_position(test_cell[0], test_cell[1], 3) # He hits the up arrow, and sees his changes committed and the current cell move up. self.human_key_press(key_codes.UP) self.wait_for_cell_value(2, 2, "123") self.assert_cell_is_current_but_not_editing(2, 1) # He goes back into edit mode on cell B2, enters some data, then hits the down # arrow. His changes are committed and the current cell moves down. self.open_cell_for_editing(*test_cell) self.human_key_press(key_codes.NUMBER_4) self.human_key_press(key_codes.DOWN) self.wait_for_cell_value(2, 2, "1234") self.assert_cell_is_current_but_not_editing(2, 3) def test_editor_font_size_is_consistent(self): # Harold logs in and creates a new sheet. self.login_and_create_new_sheet() # * He enters text into a cell self.enter_cell_text(1, 1, "foo") # * He gets out a ruler and measures the text height cell_font_size_expression = "window.$('%s').css('font-size')" % (self.get_cell_css(1, 1),) cell_font_size = self.selenium.get_eval(cell_font_size_expression) # * He edits the cell again, and measures the height again while still editing self.open_cell_for_editing(1, 1) editor_font_size_expression = "window.$('%s').css('font-size')" % (self.get_cell_editor_css(),) editor_font_size = self.selenium.get_eval(editor_font_size_expression) # * He is pleased to discover the two measured heights are the same. self.assertTrue(cell_font_size.endswith('px')) self.assertEquals(cell_font_size, editor_font_size) def test_enter_single_character(self): ## This is to test for regression of a particular re-occuring bug that ## leads to single characters not committing properly. # Harold logs in and creates a new sheet. self.login_and_create_new_sheet() # He clicks on cell A1 self.click_on_cell(1, 1) # He types "1" self.human_key_press(key_codes.NUMBER_1) # He hits self.human_key_press(key_codes.ENTER) # The number "1" commits properly and appears in the cell. self.wait_for_cell_value(1, 1, "1") ================================================ FILE: dirigible/fts/tests/test_2635_SheetNameSelectedOnEdit.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # try: import unittest2 as unittest except ImportError: import unittest from functionaltest import FunctionalTest import key_codes class Test_2635_SheetNameSelectedOnEdit(FunctionalTest): def test_click_on_sheet_name(self): # * Harold logs in and creates a new sheet self.login_and_create_new_sheet() # * He clicks on the sheet name and enters a new one self.selenium.click('id=id_sheet_name') self.wait_for( lambda: self.is_element_present('id=edit-id_sheet_name'), lambda: 'editable sheetname to appear') self.human_key_press(key_codes.LETTER_A) self.human_key_press(key_codes.LETTER_B) self.wait_for( lambda: self.selenium.get_value('id=edit-id_sheet_name') == 'ab', lambda: 'sheetname to change') ================================================ FILE: dirigible/fts/tests/test_2639_SciPy_and_MpMath.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from functionaltest import FunctionalTest class Test_2639_SciPy_and_MpMath(FunctionalTest): def test_can_use_scipy_norm(self): # * Harold logs in to Dirigible and creates a new sheet self.login_and_create_new_sheet() # He imports scipy self.prepend_usercode("from scipy.stats import norm") # and accesses the cumulative normal distribution) # function from a number of cells, and gets the expected results self.enter_cell_text(1, 1, '=norm.cdf(-1000)') self.wait_for_cell_value(1, 1, '0.0') self.enter_cell_text(1, 2, '=norm.cdf(0)') self.wait_for_cell_value(1, 2, '0.5') self.enter_cell_text(1, 3, '=norm.cdf(1000)') self.wait_for_cell_value(1, 3, '1.0') def test_can_use_mpmath(self): # * Harold logs in to Dirigible and creates a new sheet self.login_and_create_new_sheet() # He imports mpmath self.prepend_usercode("import mpmath") # and accesses the mpmath sin function self.enter_cell_text(2, 2, '=mpmath.libmp.BACKEND') self.enter_cell_text(1, 1, '=mpmath.sin(1)') self.wait_for_cell_value(1, 1, "0.841470984807897") self.wait_for_cell_value(2, 2, "gmpy") ================================================ FILE: dirigible/fts/tests/test_2642_RecalcTimesInConsole.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # try: import unittest2 as unittest except ImportError: import unittest import re from functionaltest import FunctionalTest class Test_2642_RecalcTimesInConsole(FunctionalTest): def test_recalc_time_appears_in_console(self): # * Harold logs in and creates a new sheet self.login_and_create_new_sheet() # * He enters some usercode self.append_usercode('worksheet[1, 1].value = 10') # * He notes that the console tells him how long the recalc took self.wait_for_cell_value(1, 1, '10') output = self.get_console_content() recalc_time_report_re = re.compile('Took \d+\.\d\d+s') found_time_report = recalc_time_report_re.search(output) self.assertTrue(found_time_report) ================================================ FILE: dirigible/fts/tests/test_2644_AdminOmniscience.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from functionaltest import FunctionalTest class Test_2644_AdminOmniscience(FunctionalTest): user_count = 2 def test_staff_can_see_everything(self): # Harold logs in to Dirigible and creates a new sheet and puts some # super-sekrit data in it. self.login_and_create_new_sheet() self.enter_cell_text(1, 1, "My banking password") harolds_sheet_url = self.browser.current_url self.wait_for_cell_value(1, 1, 'My banking password') # He logs out, confident that it's safe from prying eyes. self.logout() # A member of Dirigible staff logs in, tries to view his sheet and # sees it. self.login(username='admin', password='admin password?') self.go_to_url(harolds_sheet_url) self.wait_for_grid_to_appear() self.wait_for_cell_value(1, 1, "My banking password") def test_non_staff_cannot_view_or_edit_other_users_sheets_or_json(self): harriet = self.get_my_usernames()[1] harold = self.get_my_username() # Harriet creates a sheet containing her private data. self.login_and_create_new_sheet(username=harriet) harriets_sheet_url = self.browser.current_url self.logout() # Before logging in, Harold tries to access Harriet's sheet using the # correct direct URL, with *her* username and the correct sheet ID. # He gets redirected to the login page self.assert_sends_to_login_page(harriets_sheet_url) self.assert_sends_to_login_page('%scalculate/' % (harriets_sheet_url,)) self.assert_sends_to_login_page( '%sset_cell_formula/' % (harriets_sheet_url,)) self.assert_sends_to_login_page( '%sget_json_grid_data_for_ui/' % (harriets_sheet_url,)) self.assert_sends_to_login_page( '%sget_json_meta_data_for_ui/' % (harriets_sheet_url,)) # After logging in, Harold tries to access the same sheet # using the correct direct URL, with *her* username and the # correct sheet ID. # He gets a 403 (Access denied) error self.login(username=harold) self.assert_HTTP_error(harriets_sheet_url, 403) self.assert_HTTP_error('%scalculate/' % (harriets_sheet_url,), 403) self.assert_HTTP_error( '%sget_json_grid_data_for_ui/' % (harriets_sheet_url,), 403) self.assert_HTTP_error( '%sget_json_meta_data_for_ui/' % (harriets_sheet_url,), 403) self.assert_HTTP_error( '%sset_cell_formula/' % (harriets_sheet_url,), 403) ================================================ FILE: dirigible/fts/tests/test_2650_UsercodeSandbox.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from textwrap import dedent try: import unittest2 as unittest except ImportError: import unittest from functionaltest import FunctionalTest class Test_2650_UsercodeSandbox(FunctionalTest): def test_dirigible_package_is_off_limits(self): # * Harold logs in and creates a new sheet self.login_and_create_new_sheet() # * He puts on his black hat and tries to access the sheets that # belong to other users self.prepend_usercode('from dirigible.sheet.models import User') # * Dirigible notices his naughtiness and stops it self.wait_for_console_content(dedent(''' ImportError: No module named models User code line 1''')[1:]) def test_dirigible_settings_not_accessible(self): # * Harold logs in and creates a new sheet self.login_and_create_new_sheet() # * He puts on his black hat and tries to access dirigible settings self.prepend_usercode('import dirigible.settings') # * Dirigible notices his naughtiness and stops it self.wait_for_console_content(dedent(''' ImportError: No module named settings User code line 1''')[1:]) def test_sys_path_omits_dirigible_dirs(self): # * Harold logs in and creates a new sheet self.login_and_create_new_sheet() # * He notes that sys.path does not contain any dirigible directories self.append_usercode(dedent(''' import sys worksheet[1, 1].value = False for path in sys.path: if 'dirigible' in path: worksheet[1, 1].value = True worksheet[1, 2].value = 'done' ''')) self.wait_for_cell_value(1, 2, 'done') self.assertEquals( self.get_cell_text(1, 1), 'False', 'there was a dirigible directory in sys.path') def test_harold_looks_at_his_root_directory(self): # * Harold logs in and creates a new sheet self.login_and_create_new_sheet() # * He sees a limited subset of the filesystem self.append_usercode(dedent(''' import os worksheet[1, 1].value = sorted(os.listdir("/")) ''')[1:]) self.wait_for_cell_value(1, 1, "['dev', 'etc', 'lib', 'usr']") def test_harold_tries_to_create_a_file_in_cwd(self): # * Harold logs in and creates a new sheet self.login_and_create_new_sheet() # * He tries to create a file in the current dir self.prepend_usercode(dedent(''' import os os.mknod('foo.txt') ''')[1:]) # * Dirigible notices his naughtiness and stops it self.wait_for_console_content(dedent(''' OSError: [Errno 13] Permission denied User code line 2''')[1:]) def test_harold_tries_to_create_a_file_in_usr(self): # * Harold logs in and creates a new sheet self.login_and_create_new_sheet() # * He tries to create a file in the current dir self.prepend_usercode(dedent(''' import os os.mknod('/usr/lib/foo.txt') ''')[1:]) # * Dirigible notices his naughtiness and stops it self.wait_for_console_content(dedent(''' OSError: [Errno 13] Permission denied User code line 2''')[1:]) ================================================ FILE: dirigible/fts/tests/test_2651_SaveSheetNameOnBlur.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # try: import unittest2 as unittest except ImportError: import unittest from functionaltest import FunctionalTest from key_codes import ESCAPE class Test_2651_SaveSheetNameOnBlur(FunctionalTest): def test_blur_saves_sheet_name(self): # * Harold logs in and creates a new sheet self.login_and_create_new_sheet() # * He clicks on the sheet name and enters a new one self.selenium.click('id=id_sheet_name') self.wait_for( lambda: self.is_element_present('id=edit-id_sheet_name'), lambda: 'editable sheetname to appear') self.selenium.type('id=edit-id_sheet_name', 'clicky namey') # * He clicks away from the sheet name self.selenium.fire_event('id=edit-id_sheet_name', 'blur') # * and notes that his sheet now has the new name self.wait_for( lambda: self.get_text('id=id_sheet_name') == 'clicky namey', lambda: 'sheet name to be updated was "%s"' % (self.get_text('id=id_sheet_name'),) ) # * and the sheet name is not in edit mode self.assertFalse(self.is_element_present('id=edit-id_sheet_name')) def test_cancel_instruction_present(self): # * Harold logs in and creates a new sheet self.login_and_create_new_sheet() # * He clicks on the sheet name and enters a new one original_name = self.get_text('id=id_sheet_name') self.selenium.click('id=id_sheet_name') self.wait_for( lambda: self.is_element_present('id=edit-id_sheet_name'), lambda: 'editable sheetname to appear') # * He enters a new name for the sheet self.selenium.type('id=edit-id_sheet_name', "don't really want this") # * but then decides that he doesn't want that name, so he hits ESC. self.human_key_press(ESCAPE) # * The sheet name reverts to the original self.assertEquals(self.get_text('id=id_sheet_name'), original_name) ================================================ FILE: dirigible/fts/tests/test_2652_CommitCellOnBlur.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # try: import unittest2 as unittest except ImportError: import unittest from functionaltest import FunctionalTest import key_codes class Test_2652_CommitCellOnBlur(FunctionalTest): def test_blur_cell_saves_cell_contents(self): # * Harold logs in and creates a new sheet self.login_and_create_new_sheet() # * He edits a cell and types, but doesn't press enter self.open_cell_for_editing(1, 2) self.type_into_cell_editor_unhumanized("='hello'") # * He clicks on another cell self.click_on_cell(2, 3) # * and notes that his edit has been commited self.wait_for_cell_value(1, 2, "hello") # * He types into the new cell, again without pressing enter self.open_cell_for_editing(2, 3) self.type_into_cell_editor_unhumanized("='goodbye'") # * and clicks away to something that isn't a cell. self.selenium.click('id=id_console') # * the second edit is also committed self.wait_for_cell_value(2, 3, "goodbye") def test_blur_formula_bar_saves_cell_contents(self): # * Harold logs in and creates a new sheet self.login_and_create_new_sheet() # * He double-clicks on a cell self.open_cell_for_editing(1, 2) # and types into the formula bar, but doesn't press enter self.click_formula_bar() self.human_key_press(key_codes.NUMBER_1) self.human_key_press(key_codes.NUMBER_2) self.human_key_press(key_codes.NUMBER_3) # * He clicks on another cell self.click_on_cell(2, 3) # * and notes that his edit has been commited self.wait_for_cell_value(1, 2, "123") # * He edits the new cell, again using the formula bar and without pressing enter self.open_cell_for_editing(2, 3) self.click_formula_bar() self.human_key_press(key_codes.NUMBER_4) self.human_key_press(key_codes.NUMBER_5) self.human_key_press(key_codes.NUMBER_6) # * and clicks away self.selenium.click('id=id_console') # * the second edit is also committed self.wait_for_cell_value(2, 3, "456") ================================================ FILE: dirigible/fts/tests/test_2653_UsernameFocusedOnLoginPage.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # # Check added to T2525 ================================================ FILE: dirigible/fts/tests/test_2654_CtrlSSavesUsercode.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from __future__ import with_statement from functionaltest import FunctionalTest import key_codes class Test_2654_CtrlSSavesUsercode(FunctionalTest): def test_ctrl_s_saves_usercode(self): # * Harold logs in and creates a new sheet self.login_and_create_new_sheet() # * He enters some usercode self.selenium.get_eval('window.editor.focus()') self.enter_usercode('worksheet[1, 1].value = 5', commit_change=False) # * and presses Ctrl-S with self.key_down(key_codes.CTRL): self.human_key_press(key_codes.LETTER_S) # * His usercode gets saved and a recalculation starts #self.wait_for_cell_value(1, 1, '5') # He types a few more characters, which should appear in the usercode # box, because the save-as dialog didn't appear to capture them self.human_key_press(key_codes.LETTER_A) self.human_key_press(key_codes.LETTER_A) self.human_key_press(key_codes.LETTER_A) ## NB enter_usercode leaves the cursor at the end of the editor. self.wait_for_usercode_editor_content( 'worksheet[1, 1].value = 5aaa' ) ================================================ FILE: dirigible/fts/tests/test_2678_GlobalStateNotShared.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from functionaltest import FunctionalTest, snapshot_on_error, Url from textwrap import dedent class Test_2678_GlobalStateNotShared(FunctionalTest): @snapshot_on_error def test_global_state_not_shared(self): # * Harold logs in to Dirigible self.login() # * Switching with inhuman speed between several browser windows, # in each he creates a new sheet with a unique identifier and # user code that adds the identifier to sys.path try: number_of_windows = 5 window_ids = [] for sheet_number in range(number_of_windows): window_id = "sheet_window_%s" % (sheet_number,) self.selenium.open_window(Url.NEW_SHEET, window_id) self.selenium.wait_for_pop_up(window_id, timeout=20000) self.selenium.select_window(window_id) self.wait_for_grid_to_appear() self.append_usercode(dedent(''' import sys sys.path.append(%r) worksheet[2, 1].value = %r worksheet[1, 1].value = sys.path''' % (window_id, window_id))) window_ids.append(window_id) # He ensures that they all finish the recalc for window_id in window_ids: self.selenium.select_window(window_id) self.wait_for_cell_value(2, 1, window_id, timeout_seconds=30) self.wait_for_console_content('') sys_path_str = self.get_cell_text(1, 1) self.assertTrue(window_id in sys_path_str) for w_id in window_ids: if w_id == window_id: continue if w_id in sys_path_str: self.fail('Window %r had the sys.path from window %r: %r' % (window_id, w_id, sys_path_str)) finally: # Back to our main window #self.selenium.select_window(0) pass ================================================ FILE: dirigible/fts/tests/test_2682_CellAccessUsingA1.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from textwrap import dedent from functionaltest import FunctionalTest class Test_2682_CellAccessUsingA1(FunctionalTest): def test_cell_access(self): # * Harold logs in to Dirigible and creates a new sheet self.login_and_create_new_sheet() # * he uses some convenient ways to find cells in a worksheet object self.enter_usercode(dedent(''' worksheet[1, 2].value = 'a2' worksheet["B", 4].value = 'b4' worksheet["C6"].value = 'c6' worksheet.D8.value = 'd8' ''')) # * which seems to work as he expects self.wait_for_cell_value(1, 2, 'a2') self.wait_for_cell_value(2, 4, 'b4') self.wait_for_cell_value(3, 6, 'c6') self.wait_for_cell_value(4, 8, 'd8') ================================================ FILE: dirigible/fts/tests/test_2685_ChangePassword.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # import time from functionaltest import FunctionalTest, USER_PASSWORD class Test_2685_ChangePassword(FunctionalTest): def test_change_password(self): # * Harold logs in to Dirigible. self.login() # He decides to change his password. He clicks on an appropriate-looking button # in his dashboard. self.assertFalse(self.selenium.is_visible("id=id_change_password_form")) self.selenium.click('id=id_change_password_button') # He is presented with a form requiring him to enter his current password once # and the new password twice and OK and Cancel buttons. self.wait_for_element_visibility("id=id_change_password_form", True) self.wait_for_element_visibility("id=id_change_password_current_password", True) self.wait_for_element_visibility("id=id_change_password_new_password", True) self.wait_for_element_visibility("id=id_change_password_new_password_again", True) self.wait_for_element_visibility("id=id_change_password_ok_button", True) self.wait_for_element_visibility("id=id_change_password_cancel_button", True) # He enters nothing and submits self.selenium.click('id=id_change_password_ok_button') # He gets an appropriate error. self.wait_for_element_text('id=id_change_password_error', 'Current password incorrect.') # He enters the wrong current password and new passwords that are the same. self.selenium.type('id=id_change_password_current_password', 'random incorrect password') self.selenium.type('id=id_change_password_new_password', 'newpassword') self.selenium.type('id=id_change_password_new_password_again', 'newpassword') self.selenium.click('id=id_change_password_ok_button') # He gets an appropriate error. self.wait_for_element_text('id=id_change_password_error', 'Current password incorrect.') # He enters the right current password and two different new passwords self.selenium.type('id=id_change_password_current_password', USER_PASSWORD) self.selenium.type('id=id_change_password_new_password', 'newpassword') self.selenium.type('id=id_change_password_new_password_again', 'newpassword different') self.selenium.click('id=id_change_password_ok_button') # He gets an appropriate error. self.wait_for_element_text('id=id_change_password_error', 'Please provide the new password twice for confirmation.') # He gives up in frustration and hits a cancel button on the form. self.selenium.click('id=id_change_password_cancel_button') # The password change form disappears self.wait_for( lambda: not self.selenium.is_visible("id=id_change_password_form"), lambda: "password change form to hide" ) # and the button re-appears self.wait_for_element_visibility('id=id_change_password_button', True) # He opens the form again and notes that his old error message is gone and the password fields have been reset self.selenium.click('id=id_change_password_button') self.wait_for_element_text('id=id_change_password_error', '') self.assertEquals(self.selenium.get_value('id=id_change_password_current_password'), '') self.assertEquals(self.selenium.get_value('id=id_change_password_new_password'), '') self.assertEquals(self.selenium.get_value('id=id_change_password_new_password_again'), '') # He logs out. self.logout() # After tea and consolation from a sympathetic Harriet, he decides to try again. # He logs in using his original password. self.login() # He clicks the change password button. self.selenium.click('id=id_change_password_button') # This time he manages to get his old password right and enters the # same new password both times. self.selenium.type('id=id_change_password_current_password', USER_PASSWORD) self.selenium.type('id=id_change_password_new_password', 'newpassword') self.selenium.type('id=id_change_password_new_password_again', 'newpassword') self.selenium.click('id=id_change_password_ok_button') # After a few seconds, the form goes away self.wait_for( lambda: not self.selenium.is_visible("id=id_change_password_form"), lambda: "password change form to hide" ) # He is told "thank you". self.wait_for_element_visibility("id=id_change_password_success", True) self.wait_for_element_text('id=id_change_password_success', 'Your password has been changed.') # then after a couple of seconds, the thank-you goes away self.wait_for( lambda: not self.selenium.is_visible("id=id_change_password_success"), lambda: "password change success notice to hide", timeout_seconds=6 ) # He logs out self.logout() # He tries to log in with the old password. self.login() # It doesn't work. self.wait_for_element_text("id=id_login_error", "The user name or password is incorrect. Please try again.") # He tries the new password. self.login(password='newpassword') # It works. expected = "%s's Dashboard: Dirigible" % (self.get_my_username(),) self.assertEquals(self.browser.title, expected) # He celebrates. ================================================ FILE: dirigible/fts/tests/test_2689_DontClearCellEditorWhenRecalcsHitClient.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from textwrap import dedent from time import sleep from functionaltest import FunctionalTest import key_codes class Test_2689_DontClearCellEditorOrFormulaBarWhenRecalcsHitClient(FunctionalTest): def test_cell_editor_not_cleared(self): # * Harold logs in and creates a new sheet self.login_and_create_new_sheet() # * He creates a sheet that will take at least 5 seconds to recalc. self.append_usercode(dedent(""" import time time.sleep(25) """)) # * He waits for the first recalc to complete self.wait_for_spinner_to_stop(timeout_seconds=30) # * He enters 1 in cell A1 self.enter_cell_text(1, 1, "1") # * Without waiting for the recalc to complete, he edits cell A2 and types "test" but doesn't hit return. self.open_cell_for_editing(1, 2) self.wait_for_cell_to_enter_edit_mode(1, 2) self.type_into_cell_editor_unhumanized("test") # * He waits for more than 25 seconds, and notes that the recalc ends. sleep(25) self.wait_for_spinner_to_stop(timeout_seconds=20) # * The cell editor still contains "test" self.wait_for_cell_editor_content("test") def test_formula_bar_not_cleared(self): # * Harold logs in and creates a new sheet self.login_and_create_new_sheet() # * He creates a sheet that will take at least 5 seconds to recalc. self.append_usercode(dedent(""" import time time.sleep(25) """)) # * He waits for the first recalc to complete self.wait_for_spinner_to_stop(timeout_seconds=30) # * He enters 1 in cell A1 self.enter_cell_text(1, 1, "1") # * Without waiting for the recalc to complete, he edits cell A2 and types "123" into the formula # bar, but doesn't hit return. self.open_cell_for_editing(1, 2) self.wait_for_cell_to_enter_edit_mode(1, 2) self.click_formula_bar() self.human_key_press(key_codes.NUMBER_1) self.human_key_press(key_codes.NUMBER_2) self.human_key_press(key_codes.NUMBER_3) # * He waits for more than 25 seconds, and notes that the recalc ends. sleep(25) self.wait_for_spinner_to_stop(timeout_seconds=20) # * The formula bar still contains "123" self.assert_formula_bar_contains("123") ================================================ FILE: dirigible/fts/tests/test_2690_FocusShouldStartInGrid.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from functionaltest import FunctionalTest import key_codes class test_2690_FocusShouldStartInGrid(FunctionalTest): def test_focus(self): # * Harold logs in to Dirigible and creates a new sheet self.login_and_create_new_sheet() # * The focus is initially in A1 # (not in text editor where it is annoying since clicking on # grid then causes unwanted recalcs. Especially annoying after # loading a large sheet) # Hence, Harold starts typing... self.human_key_press(key_codes.NUMBER_1) self.human_key_press(key_codes.NUMBER_2) self.human_key_press(key_codes.NUMBER_3) # * The formula he typed appears in cell A1's edit box self.wait_for_cell_to_enter_edit_mode(1, 1) self.wait_for_cell_editor_content('123') ================================================ FILE: dirigible/fts/tests/test_2691_AllowSettingValuesFromUsercodeBeforeLoadConstants.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from textwrap import dedent try: import unittest2 as unittest except ImportError: import unittest from functionaltest import FunctionalTest class Test_2691_AllowSettingValuesFromUsercodeBeforeLoadConstants(FunctionalTest): def test_load_constants_doesnt_trash_cells(self): # * Harold logs in and creates a new sheet self.login_and_create_new_sheet() # * He adds stuff to the start of the usercode to set a value on a cell, before load_constants # (which previously erroneously cleared cells with no set formula) self.prepend_usercode(dedent(""" worksheet[1, 1].value = 'wibble' """)) # * When the recalc is completed, the cell shows the value self.wait_for_cell_value(1, 1, 'wibble') ================================================ FILE: dirigible/fts/tests/test_2701_NameResolutionWorks.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from textwrap import dedent from functionaltest import FunctionalTest class Test_2701_NameResolutionWorks(FunctionalTest): def test_name_resolution(self): # * Harold logs in to Dirigible and creates a new sheet self.login_and_create_new_sheet() # * He writes some usercode that does something to test # name resolution. self.enter_usercode(dedent(""" from urllib2 import urlopen, URLError try: connection = urlopen("http://www.google.com/") worksheet[1, 1].value = len(connection.read()) connection.close() except URLError, e: worksheet[1, 1].value = e.reason[1] except Exception, e: worksheet[1, 1].value = str(e) """)) # * It runs OK. self.wait_for_spinner_to_stop() size = int(self.get_cell_text(1, 1)) self.assertTrue(size > 0) ================================================ FILE: dirigible/fts/tests/test_2702_HttpsInChrootJail.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from textwrap import dedent from functionaltest import FunctionalTest class Test_2702_HttpsInChrootJail(FunctionalTest): def test_numpy_tutorial_create_arrays(self): # * Harold wants to access https URLs from his Dirigible sheets # * He logs in and creates a new sheet self.login_and_create_new_sheet() # * .. and adds a get for a https URL in an S3 bucket self.prepend_usercode(dedent(''' from urllib2 import urlopen connection = urlopen('https://s3.amazonaws.com/oecd-data/oecd-data.csv') content = connection.read() worksheet.A1.value = 'Canada' in content ''')) # * and notes that he gets the expected content self.wait_for_cell_value(1, 1, 'True') ================================================ FILE: dirigible/fts/tests/test_2704_OldStyleClassesInTheGrid.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # import re from textwrap import dedent from functionaltest import FunctionalTest class Test_2704_OldStyleClassesInTheGrid(FunctionalTest): def test_put_old_style_classes_in_grid(self): # * Harold logs in to Dirigible and creates a new sheet self.login_and_create_new_sheet() # * He defines an old-style class in the usercode self.enter_usercode(dedent(''' class Basilisk(): pass load_constants(worksheet) evaluate_formulae(worksheet) ''')) # * He puts an instance of it in the grid self.enter_cell_text(1, 1, "=Basilisk()") # * The formatted value is something sane. self.wait_for_cell_value(1, 1, re.compile(r'<__builtin__.Basilisk instance at 0x.*>')) # * He references the class itself from another cell, just for kicks. self.enter_cell_text(1, 2, "=Basilisk") # * The formatted value is equally sane. self.wait_for_cell_value(1, 2, '__builtin__.Basilisk') # * He refreshes the page. self.selenium.refresh() self.wait_for_grid_to_appear() # * Everything still looks OK. self.wait_for_cell_value(1, 1, re.compile(r'<__builtin__.Basilisk instance at 0x.*>')) self.wait_for_cell_value(1, 2, '__builtin__.Basilisk') # * Disappointed at not being about to break Dirigible, he goes out to take # potshots at the Goodyear blimp with a shotgun. ================================================ FILE: dirigible/fts/tests/test_2711_ImportExcel.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from functionaltest import FunctionalTest, PAGE_LOAD_TIMEOUT, Url import os SHEET1 = 'first sheet' SHEET2 = '2nd' EMPTY_SHEET = 'empty sheet' class Test_2711_Import_Excel(FunctionalTest): def test_can_import_excel_values_only_from_sheet_page(self): # Harold has an excel file he wants to import into a cloud-based # python-infused spreadsheet # * Harold logs in to Dirigible self.login_and_create_new_sheet() # * He spots the 'import' button self.wait_for_element_visibility('id=id_import_button', True) self.assertEquals( self.selenium.get_attribute('id=id_import_button@alt'), "Import a file" ) self.assertEquals( self.selenium.get_attribute('id=id_import_button@title'), "Import a file" ) # * He clicks the import button self.selenium.click('id=id_import_button') # * He is presented with a jquery dialog that contains a file input element self.wait_for_element_visibility('id=id_import_form', True) self.wait_for_element_visibility('id=id_import_form_file', True) self.wait_for_element_visibility('id=id_import_form_upload_csv_button', False) self.wait_for_element_visibility('id=id_import_form_upload_xls_values_button', False) self.wait_for_element_visibility('id=id_import_form_cancel_button', True) # * Harold panics at seeing something new, and clicks the cancel button self.selenium.click('id=id_import_form_cancel_button') # the dialog disappears self.wait_for_element_visibility('id=id_import_form', False) # Harold, keen to get data imported, summons up the strength to try again self.selenium.click('id=id_import_button') # the dialog reappears. self.wait_for_element_visibility('id=id_import_form', True) # * He clicks on the file browse button. He is presented with a # file-open dialog, and chooses a suitable excel file file_name = os.path.join( os.path.dirname(__file__), 'test_data', 'T2711-import-excel.xls' ) self.set_filename_for_upload(file_name, 'id=id_import_form_file') # the upload excel buttons appear self.wait_for_element_visibility( 'id=id_import_form_upload_xls_values_button', True) # so he clicks one self.selenium.click('id=id_import_form_upload_xls_values_button') # * ...and gets redirected to his dashboard self.selenium.wait_for_page_to_load(10000) self.selenium.open(Url.ROOT) self.selenium.wait_for_page_to_load(10000) # He notices 3 new sheets added to his dashboard page_text = self.selenium.get_html_source() self.assertTrue('T2711-import-excel - first sheet' in page_text) self.assertTrue('T2711-import-excel - 2nd' in page_text) self.assertTrue('T2711-import-excel - error sheet' in page_text) self.assertTrue('T2711-import-excel - empty sheet' not in page_text) # He visits the first one to check that the values there are # the same as the ones in the Excel file self.selenium.click('link=T2711-import-excel - first sheet') self.selenium.wait_for_page_to_load(10000) self.wait_for_grid_to_appear() self.wait_for_cell_value(1, 1, "Spell") self.wait_for_cell_value(2, 1, "Arc") self.wait_for_cell_value(3, 1, "Bar") self.wait_for_cell_value(1, 2, "attack") self.wait_for_cell_value(14, 8, "E") # he goes back to the dashboard self.selenium.open(Url.ROOT) self.selenium.wait_for_page_to_load(10000) # He visits the second sheet to check that the values there are # the same as the ones in the Excel file self.selenium.click('link=T2711-import-excel - 2nd') self.selenium.wait_for_page_to_load(10000) self.wait_for_grid_to_appear() self.wait_for_cell_value(1, 1, "sauce") self.wait_for_cell_value(3, 4, "27.5") # he goes back to the dashboard self.selenium.open(Url.ROOT) self.selenium.wait_for_page_to_load(10000) # He visits the third sheet to check that the values there are # the same as the ones in the Excel file self.selenium.click('link=T2711-import-excel - error sheet') self.selenium.wait_for_page_to_load(10000) self.wait_for_grid_to_appear() self.wait_for_cell_value(1, 1, "1979-10-08 00:00:00") self.click_on_cell(2, 1) self.wait_for_formula_bar_contents("=#NUM!") self.click_on_cell(3, 1) self.wait_for_formula_bar_contents("=#DIV/0!") def test_bad_files_are_gracefully_handled(self): # Harold thinks that if he imports an image file, # it will appear in his spreadsheet # * He logs in to Dirigible and creates a nice shiny new sheet self.login_and_create_new_sheet() sheet_url = self.browser.current_url # * He clicks the import button self.selenium.click('id=id_import_button') # * He is presented with a jquery dialog that contains a file input # element self.wait_for_element_visibility('id=id_import_form', True) # * He clicks on the browse button # * He is presented with a file-open dialog, and chooses his image file file_name = os.path.join( os.path.dirname(__file__), 'test_data', 'T2711-badly-named-png.xls' ) self.set_filename_for_upload(file_name, 'id=id_import_form_file') # He clicks the upload excel values only button self.selenium.click('id=id_import_form_upload_xls_values_button') # * ...and waits for the page to refresh self.selenium.wait_for_page_to_load(PAGE_LOAD_TIMEOUT) # * He is presented with an appropriate error page self.assertEquals( self.browser.title, "Excel Import Error: Dirigible") self.assertEquals( self.get_text("id=id_server_error_title"), "Could not import Excel file" ) error_text = self.get_text("id=id_server_error_text") msg = "Sorry, the file you uploaded was not imported" self.assertTrue(msg in error_text) # * There is a link back to his account page, which he follows self.click_link('id_account_link') # And finds himself back on his sheet page. self.assertEquals(self.browser.current_url, Url.ROOT) ================================================ FILE: dirigible/fts/tests/test_2712_ImportCSV.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from functionaltest import FunctionalTest, PAGE_LOAD_TIMEOUT import os class Test_2712_ImportCSV(FunctionalTest): def test_can_import_excel_generated_csv_to_cursor_position(self): file_name = 'excel_generated_csv.csv' # Harold has a csv file he wants to import into a cloud-based # python-infused spreadsheet # * Harold logs in to Dirigible and creates a nice shiny new sheet self.login_and_create_new_sheet() # * After weeks of frustration at being unable to get any data into the app, # it's with great joy that he spots a new button called 'import' self.wait_for_element_visibility('id=id_import_button', True) self.assertEquals( self.selenium.get_attribute('id=id_import_button@alt'), "Import a file" ) self.assertEquals( self.selenium.get_attribute('id=id_import_button@title'), "Import a file" ) # With preternatural insightfulness, he guesses the button will import # to the current cursor position. Accordingly, he gives himself a # little wriggle room self.click_on_cell(2, 2) # * He clicks the import CSV button self.selenium.click('id=id_import_button') # * He is presented with a jquery dialog that contains a file input element self.wait_for_element_visibility('id=id_import_form', True) self.wait_for_element_visibility('id=id_import_form_file', True) self.wait_for_element_visibility('id=id_import_form_upload_csv_button', False) self.wait_for_element_visibility('id=id_import_form_upload_xls_values_button', False) self.wait_for_element_visibility('id=id_import_form_cancel_button', True) #Harold chooses a file, but changes his mind and clicks cancel file_name = os.path.join( os.path.dirname(__file__), 'test_data', file_name ) self.set_filename_for_upload(file_name, 'id=id_import_form_file') self.selenium.click('id=id_import_form_cancel_button') # the dialog disappears self.wait_for_element_visibility('id=id_import_form', False) # harold, keen to get data imported, summons up the strength to try again self.selenium.click('id=id_import_button') # the dialog reappears self.wait_for_element_visibility('id=id_import_form', True) # his previous file choice doesn't self.assertEquals( self.selenium.get_value('id=id_import_form_file'), '' ) # * He clicks on the browse button * He is presented with a file-open # dialog, and chooses a suitable csv file self.set_filename_for_upload(file_name, 'id=id_import_form_file') # He spots a radio button, which is defaulted to the 'excel' option self.wait_for_element_visibility( 'css=input[type="radio"][name="csv_encoding"][value="excel"]', True ) self.wait_for_element_visibility( 'css=input[type="radio"][name="csv_encoding"][value="other"]', True ) self.assertEquals( self.selenium.get_value( 'css=input[type="radio"][name="csv_encoding"][value="excel"]' ), 'on' ) # so he clicks the upload button self.wait_for_element_visibility('id=id_import_form_upload_csv_button', True) self.selenium.click('id=id_import_form_upload_csv_button') # * ...and waits for the page to refresh self.selenium.wait_for_page_to_load(PAGE_LOAD_TIMEOUT) self.wait_for_grid_to_appear() # * and is mightily pleased when his data appears at the cursor position. self.wait_for_cell_value(2, 2, 'some text') self.wait_for_cell_value(3, 2, 'text with quotes "\'""\'"') # * In order to check that the cell with a carraige return is imported # ok, he has to check its value via the console, since the \n is # converted to something else for display in the cell #self.wait_for_cell_value(4, 2, 'text with a \ncarriage return') self.append_usercode('print worksheet.D2.value == "text with a \\ncarriage return"') self.wait_for_console_content('True') self.wait_for_cell_value(2, 3, 'some european characters:') self.wait_for_cell_value(3, 3, u'Herg\xe9') self.wait_for_cell_value(2, 4, 'some european money:') self.wait_for_cell_value(3, 4, u'pounds: \xa3') self.wait_for_cell_value(4, 4, u'euros : \u20ac') self.wait_for_cell_value(2, 5, 'numbers') self.wait_for_cell_value(2, 6, '1') self.wait_for_cell_value(3, 6, '2') self.wait_for_cell_value(4, 6, '3000000000') def test_can_import_utf8_csv(self): # Harold has a kawaii csv file he wants to import into a cloud-based # python-infused spreadsheet file_name = 'japanese.csv' # * He creates a new sheet and clicks the import CSV button self.login_and_create_new_sheet() self.selenium.click('id=id_import_button') # the dialog reappears self.wait_for_element_visibility('id=id_import_form', True) # * He clicks on the browse button * He is presented with a file-open # dialog, and chooses a suitable csv file file_name = os.path.join( os.path.dirname(__file__), 'test_data', file_name ) self.set_filename_for_upload(file_name, 'id=id_import_form_file') # He spots a radio button, which is defaulted to the 'excel' option self.wait_for_element_visibility( 'css=input[type="radio"][name="csv_encoding"][value="excel"]', True ) self.wait_for_element_visibility( 'css=input[type="radio"][name="csv_encoding"][value="other"]', True ) self.assertEquals( self.selenium.get_value( 'css=input[type="radio"][name="csv_encoding"][value="excel"]' ), 'on' ) # so he changes the radio button option to 'other' self.selenium.check( 'css=input[type="radio"][name="csv_encoding"][value="other"]' ) # and clicks the upload button self.wait_for_element_visibility('id=id_import_form_upload_csv_button', True) self.selenium.click('id=id_import_form_upload_csv_button') # * ...and waits for the page to refresh self.selenium.wait_for_page_to_load(PAGE_LOAD_TIMEOUT) self.wait_for_grid_to_appear() # * and is mightily pleased when his kanji appear self.wait_for_cell_value(1, 1, u'\u65b0\u4e16\u7d00\u30a8\u30f4\u30a1\u30f3\u30b2\u30ea\u30aa\u30f3') def test_bad_files_are_gracefully_handled(self): # Harold thinks that if he imports an image file, # it will appear in his spreadsheet file_name = os.path.join( os.path.dirname(__file__), 'test_data', 'import_csv_button.png') # * He logs in to Dirigible and creates a nice shiny new sheet self.login_and_create_new_sheet() sheet_url = self.browser.current_url # * He clicks the import toolbar button self.selenium.click('id=id_import_button') # * He is presented with a jquery dialog that contains a file input element self.wait_for_element_visibility('id=id_import_form', True) self.wait_for_element_visibility('id=id_import_form_file', True) self.wait_for_element_visibility('id=id_import_form_cancel_button', True) # * He clicks on the browse button # * He is presented with a file-open dialog, and chooses his image file self.set_filename_for_upload(file_name, 'id=id_import_form_file') # He clicks the upload button self.wait_for_element_visibility('id=id_import_form_upload_csv_button', True) self.selenium.click('id=id_import_form_upload_csv_button') # * ...and waits for the page to refresh self.selenium.wait_for_page_to_load(PAGE_LOAD_TIMEOUT) # * He is presented with an appropriate error page self.assertEquals(self.browser.title, "CSV Import Error: Dirigible") self.assertEquals( self.get_text("id=id_server_error_title"), "Could not import CSV file" ) error_text = self.get_text("id=id_server_error_text") msg = "Sorry, the file you uploaded was not in a recognised CSV format" self.assertTrue(msg in error_text) # * There is a link back to the sheet page, which he follows self.click_link('id_sheet_link') # And finds himself back on his sheet page. self.wait_for_grid_to_appear() self.assertEquals(self.browser.current_url, sheet_url) ================================================ FILE: dirigible/fts/tests/test_2726_FormulaBarTextSelection.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from functionaltest import FunctionalTest from key_codes import LEFT, LETTER_S class Test_2726_FormulaBarSelectionDefect(FunctionalTest): def test_can_select_text_in_formula_bar(self): # * Harold logs in to Dirigible and creates a new sheet self.login_and_create_new_sheet() long_string = '1234567890' * 10 self.enter_cell_text(1, 1, long_string) self.wait_for_cell_value(1, 1, long_string) self.click_on_cell(1, 1) self.selenium.click('id=id_formula_bar') self.human_key_press(LEFT) self.human_key_press(LEFT) self.human_key_press(LEFT) self.human_key_press(LEFT) self.human_key_press(LEFT) self.human_key_press(LEFT) self.selenium.click('id=id_formula_bar') self.human_key_press(LETTER_S) self.human_key_press(LETTER_S) self.assertTrue( self.selenium.get_value( self.get_formula_bar_locator()).endswith( '1234ss567890' ) ) ================================================ FILE: dirigible/fts/tests/test_2734_ClearCells.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # try: import unittest2 as unittest except ImportError: import unittest from functionaltest import FunctionalTest import key_codes from textwrap import dedent class Test_2734_ClearCells(FunctionalTest): def test_delete_key_clears_selected_cells(self): self.assert_key_deletes_cells(key_codes.DELETE) def test_backspace_key_clears_selected_cells(self): self.assert_key_deletes_cells(key_codes.BACKSPACE) def assert_key_deletes_cells(self, key_code): # * Harold logs in and creates a new sheet self.login_and_create_new_sheet() # * He enters some data in A1:A3 self.enter_cell_text(1, 1, 'a1') self.enter_cell_text(1, 2, 'a2') self.enter_cell_text(1, 3, 'a3') self.wait_for_cell_value(1, 3, 'a3') # * He clicks on A1 and hits delete self.click_on_cell(1, 1) self.human_key_press(key_code) # * He sees the value in A1 disappear while the others remain self.wait_for_cell_value(1, 1, '') self.wait_for_cell_value(1, 2, 'a2') self.wait_for_cell_value(1, 3, 'a3') # * He selects the range a2:a3 self.select_range_with_shift_click((1, 2), (1, 3)) # He hits delete self.human_key_press(key_code) # * He sees that all the cells are now cleared self.wait_for_cell_value(1, 1, '') self.wait_for_cell_value(1, 2, '') self.wait_for_cell_value(1, 3, '') def test_delete_key_while_editing_still_does_what_it_should(self): # * Harold logs in and creates a new sheet self.login_and_create_new_sheet() # * He enters three characters in A1 self.open_cell_for_editing(1, 1) self.human_key_press(key_codes.NUMBER_1) self.human_key_press(key_codes.NUMBER_2) self.human_key_press(key_codes.NUMBER_3) # * He moves left twice self.human_key_press(key_codes.LEFT) self.human_key_press(key_codes.LEFT) # He hits delete self.human_key_press(key_codes.DELETE) # the middle character is now missing self.wait_for_cell_editor_content('13') def test_backspace_key_while_editing_still_does_what_it_should(self): # * Harold logs in and creates a new sheet self.login_and_create_new_sheet() # * He enters three characters in A1 self.open_cell_for_editing(1, 1) self.human_key_press(key_codes.NUMBER_1) self.human_key_press(key_codes.NUMBER_2) self.human_key_press(key_codes.NUMBER_3) # * He moves left once self.human_key_press(key_codes.LEFT) # He hits backspace self.human_key_press(key_codes.BACKSPACE) # the middle character is now missing self.wait_for_cell_editor_content('13') def test_can_clear_cell_from_usercode(self): # * Harold logs in and creates a new sheet self.login_and_create_new_sheet() # * He enters some data in A1:A3 self.enter_cell_text(1, 1, 'a1') self.enter_cell_text(1, 2, 'a2') self.enter_cell_text(1, 3, 'a3') self.wait_for_cell_value(1, 3, 'a3') # * He tries to use the clear() function from usercode on a cell # and then tries to access some of the supposedly cleared attributes of the cell self.prepend_usercode(dedent(''' worksheet.a1.error = 'harold puts a deliberate pointless error in' worksheet.a1.clear() worksheet.b1.formula = str(worksheet.a1.value) worksheet.b2.formula = str(worksheet.a1.formula) worksheet.b3.formula = str(worksheet.a1.formatted_value) worksheet.b4.formula = str(worksheet.a1.error) ''')) # * He sees the value in a1 disappear self.wait_for_cell_value(1, 1, '') self.wait_for_cell_value(1, 2, 'a2') self.wait_for_cell_value(1, 3, 'a3') # * He sees his little investigations also produce the expected results self.wait_for_cell_value(2, 1, '') self.wait_for_cell_value(2, 2, 'None') self.wait_for_cell_value(2, 3, '') self.wait_for_cell_value(2, 4, 'None') def test_can_clear_cell_range_from_usercode(self): # * Harold logs in and creates a new sheet self.login_and_create_new_sheet() # * He enters some data in A1:A3 self.enter_cell_text(1, 1, 'a1') self.enter_cell_text(1, 2, 'a2') self.enter_cell_text(1, 3, 'a3') self.wait_for_cell_value(1, 3, 'a3') # * He tries to use the clear() function from usercode on a cell range self.prepend_usercode(dedent(''' worksheet.a1.error = 'harold puts a deliberate pointless error in' worksheet.a2.error = 'harold puts another deliberate pointless error in' worksheet.cell_range("a1:a2").clear() worksheet.b1.formula = str(worksheet.a1.value) worksheet.b2.formula = str(worksheet.a1.formula) worksheet.b3.formula = str(worksheet.a1.formatted_value) worksheet.b4.formula = str(worksheet.a1.error) worksheet.c1.formula = str(worksheet.a2.value) worksheet.c2.formula = str(worksheet.a2.formula) worksheet.c3.formula = str(worksheet.a2.formatted_value) worksheet.c4.formula = str(worksheet.a2.error) ''')) # * He sees the value in a1 and a2 disappear self.wait_for_cell_value(1, 1, '') self.wait_for_cell_value(1, 2, '') self.wait_for_cell_value(1, 3, 'a3') # * He sees his little investigations also produce the expected results self.wait_for_cell_value(2, 1, '') self.wait_for_cell_value(2, 2, 'None') self.wait_for_cell_value(2, 3, '') self.wait_for_cell_value(2, 4, 'None') self.wait_for_cell_value(3, 1, '') self.wait_for_cell_value(3, 2, 'None') self.wait_for_cell_value(3, 3, '') self.wait_for_cell_value(3, 4, 'None') ================================================ FILE: dirigible/fts/tests/test_2735_CtrlKeysArePassedOnToBrowser.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from __future__ import with_statement from functionaltest import FunctionalTest import key_codes class Test_2735_CtrlKeysArePassedToBrowser(FunctionalTest): def test_ctrl_t(self): # * Harold logs in to Dirigible and creates a new sheet self.login_and_create_new_sheet() # * He clicks the grid then types ctrl-t self.click_on_cell(1, 1) with self.key_down(key_codes.CTRL): self.human_key_press(key_codes.LETTER_T) # * A new tab has been opened. NB no way to test this # was found after quite a while investigating. # * Harold switches back self.selenium.select_window('null') # * The current cell is not being edited. self.assert_cell_is_current_but_not_editing(1, 1) # * The current cell does not contain a 't' self.assertEquals('', self.get_cell_text(1, 1)) ================================================ FILE: dirigible/fts/tests/test_2741_Xlrd.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from functionaltest import FunctionalTest class Test_2741_Xlrd(FunctionalTest): def test_can_use_xlrd(self): # * Harold logs in to Dirigible and creates a new sheet self.login_and_create_new_sheet() # He imports xlrd self.prepend_usercode("import xlrd") self.append_usercode("worksheet.A1.value = 4") # * and it works self.wait_for_cell_value(1, 1, '4') ================================================ FILE: dirigible/fts/tests/test_2749_DisallowArbitraryKeysForWorksheet.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from functionaltest import FunctionalTest class Test_2749_DisallowArbitraryKeysForWorksheet(FunctionalTest): def test_cant_use_silly_worksheet_keys(self): # * Harold logs in to Dirigible and creates a new sheet self.login_and_create_new_sheet() # * He writes usercode to use a non-cell-reference key with the worksheet self.append_usercode("worksheet['fred'].value = 23") # * He gets an error self.wait_for_console_content("KeyError: 'fred' is not a valid cell location") ================================================ FILE: dirigible/fts/tests/test_2751_UsefulModules.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from textwrap import dedent from functionaltest import FunctionalTest class test_2751_UsefulModules(FunctionalTest): def test_can_import_useful_modules(self): # * Harold logs in to Dirigible and creates a new sheet self.login_and_create_new_sheet() # He writes some usercode that imports a bunch of modules # and then sets a cell to a value to prove that it worked. self.prepend_usercode(dedent(""" ## pycrypto from Crypto.Hash import MD5 ## sqlalchemy from sqlalchemy import * ## lxml from lxml import * ## rdflib from rdflib.graph import Graph ## geopy from geopy import * ## BeautifulSoup from BeautifulSoup import * ## mechanize from mechanize import * worksheet.A1.value = 21 """)) # The cell has the right value self.wait_for_cell_value(1, 1, "21") ================================================ FILE: dirigible/fts/tests/test_2758_LoadGridDataOnDemand.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from __future__ import with_statement from functionaltest import FunctionalTest import key_codes from textwrap import dedent class Test_2758_LoadGridDataOnDemand(FunctionalTest): def test_grid_data_loaded_on_demand_for_rows(self): # * Harold logs in to Dirigible and creates a new sheet self.login_and_create_new_sheet() # * Using usercode, he puts a blob of data far down and to # the right in the sheet. self.append_usercode(dedent(""" for col in range(1, 53): for row in range(600, 1001): worksheet[col, row].value = "(%s, %s)" % (col, row) """)) # * He waits for the recalc to complete. self.wait_for_spinner_to_stop() # He makes sure the grid has focus for the subsequent keypress self.click_on_cell(1, 1) # * then types Ctrl-End with self.key_down(key_codes.CTRL): self.human_key_press(key_codes.END) # * The grid scrolls down and to the right, but the cells are initially # empty self.wait_for_cell_to_be_visible(52, 1000) self.wait_for_cell_value(52, 1000, '') # * A "Buffering" window appears, relatively briefly self.wait_for_element_visibility("id=id_buffering_message", True) # * When the "buffering" window disappears, the data is there. self.wait_for_element_visibility("id=id_buffering_message", False) self.wait_for_cell_value(52, 1000, '(52, 1000)') ================================================ FILE: dirigible/fts/tests/test_2762_PythonConversion.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from functionaltest import FunctionalTest class Test_2762_PythonConversion(FunctionalTest): def test_formulas_work_properly(self): # * Harold logs in to Dirigible and creates a new sheet self.login_and_create_new_sheet() # the following formula was causing errors self.enter_cell_text(1, 1, '10') self.enter_cell_text(1, 2, '=[x * A1 for x in range(5)]') self.wait_for_cell_value(1, 2, '[0, 10, 20, 30, 40]') # as was this one self.enter_cell_text(2, 1, '10') self.enter_cell_text(2, 2, '={1 -> B1}[1]') self.wait_for_cell_value(2, 2, '10') # and this self.enter_cell_text(3, 1, '10') self.enter_cell_text(3, 2, '=(lambda x -> C1 * x)(2)') self.wait_for_cell_value(3, 2, '20') ================================================ FILE: dirigible/fts/tests/test_2770_ClearDependentCellErrors.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from functionaltest import FunctionalTest class Test_2770_ClearDependentCellErrors(FunctionalTest): def test_dependent_cell_errors_are_cleared(self): # * Harold logs in to Dirigible and creates a new sheet self.login_and_create_new_sheet() # * He enters "=1/0" into A1 self.enter_cell_text(1, 1, '=1/0') self.wait_for_spinner_to_stop() # * He enters "=A1 + 1" into A2 self.enter_cell_text(1, 2, '=A1 + 1') self.wait_for_spinner_to_stop() # * He enters "=A2 + 1" into A3 self.enter_cell_text(1, 3, '=A2 + 1') self.wait_for_spinner_to_stop() # * He confirms that both A1 and A2 have errors self.assert_cell_has_error(1, 1, 'ZeroDivisionError: division by zero') self.assert_cell_has_error( 1, 2, "TypeError: unsupported operand type(s) for +: 'Undefined' and 'int'" ) self.assert_cell_has_error( 1, 3, "TypeError: unsupported operand type(s) for +: 'Undefined' and 'int'" ) # * He changes A1 to "=1" self.enter_cell_text(1, 1, '=1') # * A1's error will clear. self.wait_for_cell_value(1, 1, '1') # * A2's should too self.wait_for_cell_value(1, 2, '2') # * and A3's should too self.wait_for_cell_value(1, 3, '3') ================================================ FILE: dirigible/fts/tests/test_2774_ExportCSV.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from __future__ import with_statement from os import path import urllib2 from urlparse import urljoin from functionaltest import FunctionalTest, PAGE_LOAD_TIMEOUT class Test_2774_ExportCSV(FunctionalTest): def test_can_export_csv(self): # * Harold logs in to Dirigible and creates a nice shiny new sheet sheet_id = self.login_and_create_new_sheet() # * He puts some data including formulae and usercode-calculated stuff into a spreadsheet. # Some of the data has commas and the like in it, and there is a blank column to the # left of the data and a blank row above it. There are also non-string cell contents. self.enter_cell_text(2, 2, "Data at A2, from a constant") self.enter_cell_text(2, 3, "Data at A3, with a \\n that might need escaping") self.enter_cell_text(2, 4, "Data at A4, with 'single quotes'") self.enter_cell_text(2, 5, 'Data at A5, with "double quotes"') self.enter_cell_text(2, 6, u'Data at A6, with some unicode: Sacr\xe9 bleu! The \xa3 is expensive, compared to the \u20ac!') self.enter_cell_text(3, 2, "=2+2") self.append_usercode("worksheet.E4.value = 'hellooooo there!'") self.append_usercode( "worksheet.E5.value = ['list item 1', 'list item 2', 3]") self.append_usercode( "worksheet.E6.value = {'oats': 'a cereal which in England is fed to horses, but in Scotland forms the sustenance of the nation'}") self.append_usercode( "worksheet.E7.value = lambda x : 2 * x") self.wait_for_spinner_to_stop() # * He sees a button that talks about exporting CSVs self.wait_for_element_visibility('id=id_export_button', True) self.assertEquals( self.selenium.get_attribute('id=id_export_button@alt'), "Download as CSV file" ) self.assertEquals( self.selenium.get_attribute('id=id_export_button@title'), "Download as CSV file" ) # * He clicks on it, and sees a popup dialog with two links and a close button self.selenium.click('id=id_export_button') self.wait_for_element_visibility('id=id_export_dialog', True) self.wait_for_element_visibility('id=id_export_csv_excel_version', True) self.wait_for_element_visibility('id=id_export_csv_unicode_version', True) self.wait_for_element_visibility('id=id_export_dialog_close_button', True) self.assertEquals(self.selenium.get_value('id=id_export_dialog_close_button'), "Close") # Were he to click on the former, his browser would do whatever it normally # does when a user starts a download. This is too hard to test with # Selenium. Lets go shopping^W^W use urllib2 download_url = self.selenium.get_attribute('id=id_export_csv_excel_version@href') download_url = urljoin(self.browser.current_url, download_url) stream = self.get_url_with_session_cookie(download_url) self.assertEquals(stream.info().gettype(), "text/csv") sheet_name = 'Sheet %s' % (sheet_id,) self.assertEquals( stream.info()['Content-Disposition'], 'attachment; filename=%s.csv' % (sheet_name,) ) expected_file_name = path.join( path.dirname(__file__), "test_data", "expected_csv_file.csv" ) with open(expected_file_name) as expected_file: self.assertEquals( stream.read().replace("\r\n", "\n"), expected_file.read().replace("\r\n", "\n") ) # The file downloaded, he closes the dialog. self.selenium.click('id=id_export_dialog_close_button') self.wait_for_element_visibility('id=id_export_dialog', False) def test_can_export_unicode(self): #Harold-san has a sheet-u-des which has some zugoi kanji in: sheet_id = self.login_and_create_new_sheet() some_kanji = u'\u30bc\u30ed\u30a6\u30a3\u30f3\u30b0' self.enter_cell_text(1, 1, some_kanji) self.wait_for_spinner_to_stop() page_url = self.browser.current_url # * He clicks on a button that clearly allows him to export CSV data. self.wait_for_element_visibility('id=id_export_button', True) # * He clicks on it, and sees a popup dialog with two links self.selenium.click('id=id_export_button') self.wait_for_element_visibility('id=id_export_dialog', True) self.wait_for_element_visibility('id=id_export_csv_excel_version', True) self.wait_for_element_visibility('id=id_export_csv_unicode_version', True) # * He likes excel, so he tries to download the excel version self.click_link('id_export_csv_excel_version') # He is taken to an error page, with a helpful message suggesting he # tries again using the international version self.assertEquals(self.browser.title, "CSV Export Error: Dirigible") self.assertEquals( self.get_text("id=id_server_error_title"), "Could not export CSV file" ) error_text = self.get_text("id=id_server_error_text") msg = "Sorry, your spreadsheet contains characters that cannot be saved in Excel CSV format" self.assertTrue(msg in error_text) msg = "Please try again using the international version" self.assertTrue(msg in error_text) # * He notes there is a link back to the sheet page self.wait_for_element_visibility('id=id_sheet_link', True) # * But he spots a helpful link to the documentation, which he follows self.selenium.click('css=a[href="/documentation/import_export.html"]') self.selenium.wait_for_page_to_load(PAGE_LOAD_TIMEOUT) self.assertTrue( 'Importing and Exporting' in self.browser.title ) # He goes back to his sheet page self.selenium.open(page_url) self.wait_for_grid_to_appear() # * And tries his export again self.selenium.click('id=id_export_button') # test unicode download download_url = self.selenium.get_attribute('id=id_export_csv_unicode_version@href') download_url = urljoin(self.browser.current_url, download_url) opener = urllib2.build_opener() session_cookie = self.selenium.get_cookie_by_name('sessionid') opener.addheaders.append(('Cookie', 'sessionid=%s' % (session_cookie, ))) stream = opener.open(download_url) self.assertEquals(stream.info().gettype(), "text/csv") sheet_name = 'Sheet %s' % (sheet_id,) self.assertEquals( stream.info()['Content-Disposition'], 'attachment; filename=%s.csv' % (sheet_name,) ) expected_file_name = path.join( path.dirname(__file__), "test_data", "expected_unicode_csv.csv" ) with open(expected_file_name) as expected_file: self.assertEquals( stream.read().replace("\r\n", "\n"), expected_file.read().replace("\r\n", "\n") ) ================================================ FILE: dirigible/fts/tests/test_2781_FormulaAndFormattedValueMustBeStrings.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from textwrap import dedent from functionaltest import FunctionalTest class Test_2781_FormulaAndFormattedValueMustBeStrings(FunctionalTest): def test_str_formula_and_formatted_value(self): # * Harold wants to be perverse and do strange and unnatural things # to cells. # * He logs in and creates a new sheet self.login_and_create_new_sheet() # * He sets the formula to an object that he made self.enter_usercode(dedent(''' class HaroldsObject(object): pass worksheet.A1.formula = HaroldsObject() ''')) # * Dirigible complains. self.wait_for_console_content( 'TypeError: cell formula must be str or unicode\n User code line 5' ) # * He tries to molest the formatted_value instead self.enter_usercode(dedent(''' class HaroldsObject(object): pass worksheet.A1.formatted_value = HaroldsObject() ''')) self.wait_for_console_content( 'TypeError: cell formatted_value must be str or unicode\n User code line 5' ) ================================================ FILE: dirigible/fts/tests/test_2787_SignUp.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # import re from urlparse import urlparse, urljoin import key_codes from browser_settings import SERVER_IP from functionaltest import FunctionalTest, Url class Test_2787_SignUp(FunctionalTest): def are_password_fields_showing_error(self): return ( self.is_element_present('css=#id_password1.error') and self.is_element_present('css=#id_password2.error') ) def test_can_sign_up_from_signup_page(self): # Harold goes to the Dirigible home page self.go_to_url('/') # He notes that there are two "sign up" links, both pointing to the same URL self.click_link("id_signup_link") signup_url = self.browser.current_url self.go_to_url('/') # He follows one of them self.click_link("id_signup_call_to_action") self.assertEquals(self.browser.current_url, signup_url) # He notices a "sign up" form that requires a username, an email address, # and two copies of the same password. self.assertTrue(self.is_element_present( 'css=input#id_username')) self.assertTrue(self.is_element_present( 'css=input#id_email')) self.assertTrue(self.is_element_present( 'css=input#id_password1')) self.assertTrue(self.is_element_present( 'css=input#id_password2')) self.assertTrue(self.is_element_present( 'css=input#id_signup_button')) self.assertEquals( self.selenium.get_attribute( 'css=#id_signup_button@value'), 'Sign up') self.assertEquals( self.selenium.get_attribute( 'css=input#id_signup_button@type'), 'submit') # Being an awkward sod, he tries to sign up with no details. self.click_link('id_signup_button') # He is told off. self.assertEquals( self.get_text('id=id_username_error'), "Please enter a username." ) self.assertEquals( self.get_text('id=id_email_error'), "Please enter your email address." ) self.assertEquals( self.get_text('id=id_password1_error'), "Please enter a password." ) self.assertEquals( self.get_text('id=id_password2_error'), "Please enter a password." ) # He tries again, this time using his friend's username, # but entering sensible details for everything else. username = self.get_my_username() + "_x" duplicate_username = self.get_my_username() self.email_address = 'harold.testuser-%s@resolversystems.com' % (self.get_my_username(),) password = 'p4ssw0rd' self.selenium.type( 'id=id_username', duplicate_username) self.selenium.type( 'id=id_email', self.email_address) self.selenium.type( 'id=id_password1', password) self.selenium.type( 'id=id_password2', password) self.click_link('id_signup_button') # He is told off. self.assertEquals( self.get_text('id=id_username_error'), "This username is already taken. Please choose another." ) # He tries again with a unique username but mistypes the email address self.selenium.type( 'id=id_username', username) self.selenium.type( 'id=id_email', '@@@@@') self.selenium.type( 'id=id_password1', password) self.selenium.type( 'id=id_password2', password) self.click_link('id_signup_button') # He is told off. self.assertEquals( self.get_text('id=id_email_error'), "Please enter a valid email address." ) # He tries again with a unique username but mistypes the password self.selenium.type( 'id=id_username', username) self.selenium.type( 'id=id_email', self.email_address) self.selenium.type( 'id=id_password1', password) self.selenium.type( 'id=id_password2', "hello") ## Do the last character using native keypresses to make sure that ## all of our client-side validation JS really gets called self.selenium.focus('id=id_password2') self.human_key_press(key_codes.NUMBER_1) # Even before he submits the form, the page is grumbling at him self.wait_for( self.are_password_fields_showing_error, lambda : "Password error to appear" ) # With misplaced self-confidence, he goes ahead and clicks the button self.click_link('id_signup_button') # He is told off. self.assertEquals( self.get_text('id=id_non_field_errors'), "You must type the same password each time" ) # He finally does it correctly self.selenium.type( 'id=id_username', username) self.selenium.type( 'id=id_email', self.email_address) self.selenium.type( 'id=id_password1', password) self.selenium.type( 'id=id_password2', password) # Before he clicks the link, he confirms that there is no error in the password fields self.wait_for( lambda : not self.are_password_fields_showing_error(), lambda : "Password errors to not be there" ) self.click_link('id_signup_button') # He gets a message saying "Thank you" that tells him that an email has been # sent to his address. self.assertTrue('Thank you' in self.selenium.get_body_text()) self.assertTrue(self.email_address in self.selenium.get_body_text()) # There is a link to the Dirigible home page, which he follows and discovers # that it works. self.click_link('id_link_home') self.assertEquals(self.browser.current_url, Url.ROOT) # He checks his email, and after a short wait finds a message # from the Dirigible server, that looks like the following string: email_from, email_to, subject, message = self.pop_email_for_client(self.email_address) self.assertEquals(email_to, self.email_address) self.assertEquals(email_from, 'support@projectdirigible.com') self.assertEquals(subject, 'Dirigible Beta Sign-up') self.assertTrue('Click on the following link' in message) confirm_url_re = re.compile( r'<(http://projectdirigible\.com/signup/activate/[^>]+)>') match = confirm_url_re.search(message) self.assertTrue(match) confirmation_url = match.group(1).replace('projectdirigible.com', SERVER_IP) # He decides to type the confirmation link manually into his browser and, # inevitably, gets it completely wrong self.go_to_url(urljoin(Url.ROOT, '/signup/activate/wibble')) # He's given a kindly warning. self.assertTrue('the activation link you used was not recognised' in self.selenium.get_body_text()) # He clicks on the link in the email instead self.go_to_url(confirmation_url) body_text = self.selenium.get_body_text() # He's taken to a page that welcomes him aboard and allows him to log in. self.assertTrue( 'Welcome to Dirigible' in body_text, 'could not find "Welcome to Dirigible" on page. URL:<%s>, body text:\n%s' % (confirmation_url, body_text[:-100]) ) # He logs in, using the fields on the page. self.login(username, password, already_on_login_page=True) # He is taken to his dashboard self.assertEquals(self.browser.title, "%s's Dashboard: Dirigible" % (username,)) _, __, path, ___, ____, _____ = urlparse(self.browser.current_url) self.assertEquals(path, '/') # He's super keen to get in on the Dirigible action, so when he sees the # link saying "Create new sheet", he clicks it with gusto self.click_link('id_create_new_sheet') # He sees a dialog box promoting the tutorial self.wait_for_element_visibility('id_tutorial_promo_dialog', True) dialog_text = self.get_text('id=id_tutorial_promo_dialog') self.assertTrue('tutorial' in dialog_text.lower()) # He notes that even when the spinner stops, the focus stays on the dialog's OK # button self.wait_for_spinner_to_stop() self.assertTrue( self.is_element_focused('css=#id_tutorial_promo_dialog_close') ) # He notices a link to the tutorial inside the dialog tutorial_link_inside_dialog_locator = 'css=#id_tutorial_promo_dialog a#id_tutorial_link' self.wait_for_element_to_appear(tutorial_link_inside_dialog_locator) tutorial_link_url = self.selenium.get_attribute('%s@href' % (tutorial_link_inside_dialog_locator)) # He clicks the OK button to dismiss the dialog self.selenium.click('id=id_tutorial_promo_dialog_close') # the dialog disappears self.wait_for_element_visibility('id=id_tutorial_promo_dialog', False) # he goes to the tutorial url he remembers from earlier self.go_to_url(tutorial_link_url) # He finds himself on a page which contains the first tutorial expected_title = 'Tutorial part 1: First steps, adding Python to a spreadsheet' self.assertTrue(expected_title in self.browser.title) # He goes back to the dashboard self.go_to_url(Url.ROOT) # He creates another sheet, ready to get annoyed if he sees that dialog again... self.click_link('id_create_new_sheet') # And is happy that it isn't there, self.wait_for_grid_to_appear() self.assertFalse( self.is_element_present('id=id_tutorial_promo_dialog') ) # He logs out self.logout() # He decides that he enjoyed confirming his account so much, he'd like to # do it again. self.go_to_url(confirmation_url) # He's given a kindly warning. self.assertTrue('your account might already be activated' in self.selenium.get_body_text()) # When he returns to his email app, he sees a second email from us, email_from, email_to, subject, message = self.pop_email_for_client(self.email_address) self.assertEquals(email_to, self.email_address) self.assertEquals(email_from, 'support@projectdirigible.com') self.assertEquals(subject, 'Welcome to Dirigible') # pointing him towards the tutorial. self.assertTrue('tutorial' in message.lower()) self.assertTrue('/documentation/tutorial01.html' in message) # We also recommend that he subscribe to the Dirigible blog self.assertTrue('blog.projectdirigible.com' in message) # or follows us on Twitter self.assertTrue('twitter.com/dirigiblegrid' in message) # Satisfied, he goes back to sleep. self.assertTrue('sleep') ================================================ FILE: dirigible/fts/tests/test_2789_ErrorConsoleHTMLEscape.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from functionaltest import FunctionalTest class Test_2789_ErrorConsoleHTMLEscape(FunctionalTest): def test_error_console_output_is_escaped(self): # Harold logs in to Dirigible and creates a new sheet and puts some super-sekrit data in it. self.login_and_create_new_sheet() # * He enters usercode that writes HTML to the error console self.append_usercode("print 'not bold'") # * and notes that the result is correctly escaped self.wait_for_console_content("not bold") ================================================ FILE: dirigible/fts/tests/test_2795_Rewrite_Formulae_during_Cut_and_Paste.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from functionaltest import FunctionalTest class Test_2795_Rewrite_Formulae_during_Cut_and_Paste(FunctionalTest): def test_cut_cell_reference_to_cut_cell_is_rewritten(self): # * Harold logs in to dirigible, his favourite cloud computing tool self.login_and_create_new_sheet() # * He writes a formula in A1 that refers to cell A2 self.enter_cell_text(1, 1, '=A2') self.wait_for_spinner_to_stop() self.wait_for_cell_to_contain_formula(1, 1, '=A2') # * He uses cut & paste to move the formula (and its depenency) # from A1 to C2 self.cut_range((1, 1), (1, 2)) self.paste_range((3, 2)) self.wait_for_spinner_to_stop() # * He is please to see the formula is sensibly rewritten self.wait_for_cell_to_contain_formula(3, 2, '=C3') def test_cut_cell_reference_to_uncut_cell_is_not_rewritten(self): # * Harold logs in to dirigible, his favourite cloud computing tool self.login_and_create_new_sheet() # * He writes a formula in A1 that refers to cell B3 self.enter_cell_text(1, 1, '=B3') self.wait_for_spinner_to_stop() self.wait_for_cell_to_contain_formula(1, 1, '=B3') # * He uses cut & paste to move the formula from A1 to C2 self.cut_range((1, 1), (1, 1)) self.paste_range((3, 2)) self.wait_for_spinner_to_stop() # * He is please to see the formula remains unchanged self.wait_for_cell_to_contain_formula(3, 2, '=B3') def test_copied_cell_reference_to_copied_cell_is_rewritten(self): # * Harold logs in to dirigible, his favourite cloud computing tool self.login_and_create_new_sheet() # * He writes a formula in A1 that refers to cell A2 self.enter_cell_text(1, 1, '=A2') self.wait_for_spinner_to_stop() self.wait_for_cell_to_contain_formula(1, 1, '=A2') # * He uses copy & paste to move the formula (and its depenency) # from A1 to C2 self.copy_range((1, 1), (1, 2)) self.paste_range((3, 2)) self.wait_for_spinner_to_stop() # * He is please to see the formula is sensibly rewritten self.wait_for_cell_to_contain_formula(3, 2, '=C3') def test_copied_cell_reference_to_uncopied_cell_is_rewritten(self): # * Harold logs in to dirigible, his favourite cloud computing tool self.login_and_create_new_sheet() # * He writes a formula in A1 that refers to cell B3 self.enter_cell_text(1, 1, '=B3') self.wait_for_spinner_to_stop() self.wait_for_cell_to_contain_formula(1, 1, '=B3') # * He uses copy & paste to move the formula from A1 to C2 self.copy_range((1, 1), (1, 1)) self.paste_range((3, 2)) self.wait_for_spinner_to_stop() # * He is please to see the formula is sensibly rewritten self.wait_for_cell_to_contain_formula(3, 2, '=D4') def test_cut_cellrange_reference_to_completely_cut_cellrange_is_rewritten(self): # * Harold logs in to dirigible, his favourite cloud computing tool self.login_and_create_new_sheet() # * Harold creates a formula in A1, referencing A2:A3 self.enter_cell_text(1, 1, '=A2:A3') self.wait_for_cell_to_contain_formula(1, 1, '=A2:A3') # * He uses cut & paste to move the formula, but not its dependencies, # 2 along and 1 down self.cut_range((1, 1), (1, 3)) self.paste_range((3, 2)) self.wait_for_spinner_to_stop() # * He is please to see the formula is sensibly rewritten self.wait_for_cell_to_contain_formula(3, 2, '=C3:C4') def test_cut_cellrange_reference_to_partially_cut_cellrange_is_not_rewritten(self): # * Harold logs in to dirigible, his favourite cloud computing tool self.login_and_create_new_sheet() # * Harold creates a formula in A1, referencing A2:A3 self.enter_cell_text(1, 1, '=A2:A3') self.wait_for_cell_to_contain_formula(1, 1, '=A2:A3') # * He uses cut & paste to move the formula, but not its dependencies, # 2 along and 1 down self.cut_range((1, 1), (1, 2)) self.paste_range((3, 2)) self.wait_for_spinner_to_stop() # * He is please to see the formula is not rewritten self.wait_for_cell_to_contain_formula(3, 2, '=A2:A3') def test_cut_cellrange_reference_to_uncut_cellrange_is_not_rewritten(self): # * Harold logs in to dirigible, his favourite cloud computing tool self.login_and_create_new_sheet() # * Harold creates a formula in A1, referencing A2:A3 self.enter_cell_text(1, 1, '=A2:A3') self.wait_for_cell_to_contain_formula(1, 1, '=A2:A3') # * He uses cut & paste to move the formula, but not its dependencies, # 2 along and 1 down self.cut_range((1, 1), (1, 1)) self.paste_range((3, 2)) self.wait_for_spinner_to_stop() # * He is please to see the formula is not rewritten self.wait_for_cell_to_contain_formula(3, 2, '=A2:A3') def test_copied_cellrange_reference_to_completely_copied_cellrange_is_rewritten(self): # * Harold logs in to dirigible, his favourite cloud computing tool self.login_and_create_new_sheet() # * Harold creates a formula in A1, referencing A2:A3 self.enter_cell_text(1, 1, '=A2:A3') self.wait_for_cell_to_contain_formula(1, 1, '=A2:A3') # * He uses copy & paste to move the formula, but not its dependencies, # 2 along and 1 down self.copy_range((1, 1), (1, 3)) self.paste_range((3, 2)) self.wait_for_spinner_to_stop() # * He is please to see the formula is sensibly rewritten self.wait_for_cell_to_contain_formula(3, 2, '=C3:C4') def test_copied_cellrange_reference_to_partially_copied_cellrange_is_rewritten(self): # * Harold logs in to dirigible, his favourite cloud computing tool self.login_and_create_new_sheet() # * Harold creates a formula in A1, referencing A2:A3 self.enter_cell_text(1, 1, '=A2:A3') self.wait_for_cell_to_contain_formula(1, 1, '=A2:A3') # * He uses copy & paste to move the formula, but not its dependencies, # 2 along and 1 down self.copy_range((1, 1), (1, 2)) self.paste_range((3, 2)) self.wait_for_spinner_to_stop() # * He is please to see the formula is sensibly rewritten self.wait_for_cell_to_contain_formula(3, 2, '=C3:C4') def test_copied_cellrange_reference_to_uncopied_cellrange_is_rewritten(self): # * Harold logs in to dirigible, his favourite cloud computing tool self.login_and_create_new_sheet() # * Harold creates a formula in A1, referencing A2:A3 self.enter_cell_text(1, 1, '=A2:A3') self.wait_for_cell_to_contain_formula(1, 1, '=A2:A3') # * He uses copy & paste to move the formula, but not its dependencies, # 2 along and 1 down self.copy_range((1, 1), (1, 1)) self.paste_range((3, 2)) self.wait_for_spinner_to_stop() # * He is please to see the formula is sensibly rewritten self.wait_for_cell_to_contain_formula(3, 2, '=C3:C4') def test_outside_cell_references_to_range_cut_from_ARE_rewritten(self): # * Harold logs in to dirigible, his favourite cloud computing tool self.login_and_create_new_sheet() # * Harold creates a formula in A1, referencing B2 self.enter_cell_text(1, 1, '=B2') self.wait_for_cell_to_contain_formula(1, 1, '=B2') # * He uses cut & paste to move the cell B2, # which A1 used to point to # to position C3 self.cut_range((2, 2), (2, 2)) self.paste_range((3, 3)) self.wait_for_spinner_to_stop() # * He is pleased to see the formula in A1 # now points to the new location self.wait_for_cell_to_contain_formula(1, 1, '=C3') def test_outside_cell_ranges_pointing_inside_range_cut_from_ARE_rewritten(self): # * Harold logs in to dirigible, his favourite cloud computing tool self.login_and_create_new_sheet() # * Harold creates a formula in A1, referencing B2:C3 self.enter_cell_text(1, 1, '=B2:C3') self.wait_for_cell_to_contain_formula(1, 1, '=B2:C3') # * He uses cut & paste to move the B2:B3 # which A1 used to point to # 3 across and 4 down self.cut_range((2, 2), (4, 4)) self.paste_range((5, 6)) self.wait_for_spinner_to_stop() # * He is pleased to see the formula in A1 # now points to the new location self.wait_for_cell_to_contain_formula(1, 1, '=E6:F7') def test_absolute_references_in_copied_range_to_outside_are_not_rewritten(self): # * Harold logs in to dirigible, his favourite cloud computing tool self.login_and_create_new_sheet() # * Harold creates a formula in A2, referencing $A$1 self.enter_cell_text(1, 1, '1') self.enter_cell_text(1, 2, '=$A$1+1') self.wait_for_cell_to_contain_formula(1, 2, '=$A$1+1') # * Copies and pastes A2 to B2 self.copy_range((1, 2), (1, 2)) self.paste_range((2, 2)) self.wait_for_spinner_to_stop() # * He is pleased to see the formula in B2 is the same as the one # in A2. self.wait_for_cell_to_contain_formula(2, 2, '=$A$1+1') def test_absolute_references_in_copied_range_to_inside_are_not_rewritten(self): # * Harold logs in to dirigible, his favourite cloud computing tool self.login_and_create_new_sheet() # * Harold creates a formula in A2, referencing $A$1 self.enter_cell_text(1, 1, '1') self.enter_cell_text(1, 2, '=$A$1+1') self.wait_for_cell_to_contain_formula(1, 2, '=$A$1+1') # * Copies and pastes A1:A2 to B1:B2 self.copy_range((1, 1), (1, 2)) self.paste_range((2, 1)) self.wait_for_spinner_to_stop() # * He is pleased to see the formula in B2 is the same as the one # in A2. self.wait_for_cell_to_contain_formula(2, 2, '=$A$1+1') def test_absolute_references_outside_copied_range_to_inside_are_not_rewritten(self): # * Harold logs in to dirigible, his favourite cloud computing tool self.login_and_create_new_sheet() # * Harold creates a formula in A2, referencing $A$1 self.enter_cell_text(1, 1, '1') self.enter_cell_text(1, 2, '=$A$1+1') self.wait_for_cell_to_contain_formula(1, 2, '=$A$1+1') # * Copies and pastes A1 to B1 self.copy_range((1, 1), (1, 1)) self.paste_range((2, 1)) self.wait_for_spinner_to_stop() # * He is pleased to see the formula in A2 is unchanged self.wait_for_cell_to_contain_formula(1, 2, '=$A$1+1') def test_absolute_references_in_cut_range_to_outside_are_not_rewritten(self): # * Harold logs in to dirigible, his favourite cloud computing tool self.login_and_create_new_sheet() # * Harold creates a formula in A2, referencing $A$1 self.enter_cell_text(1, 1, '1') self.enter_cell_text(1, 2, '=$A$1+1') self.wait_for_cell_to_contain_formula(1, 2, '=$A$1+1') # * Cut and pastes A2 to B2 self.cut_range((1, 2), (1, 2)) self.paste_range((2, 2)) self.wait_for_spinner_to_stop() # * He is pleased to see the formula in B2 is the same as the one # in A2. self.wait_for_cell_to_contain_formula(2, 2, '=$A$1+1') def test_absolute_references_in_cut_range_to_inside_are_rewritten(self): # * Harold logs in to dirigible, his favourite cloud computing tool self.login_and_create_new_sheet() # * Harold creates a formula in A2, referencing $A$1 self.enter_cell_text(1, 1, '1') self.enter_cell_text(1, 2, '=$A$1+1') self.wait_for_cell_to_contain_formula(1, 2, '=$A$1+1') # * Cut and pastes A1:A2 to B1:B2 self.cut_range((1, 1), (1, 2)) self.paste_range((2, 1)) self.wait_for_spinner_to_stop() # * He is pleased to see the formula in B2 is properly rewritten self.wait_for_cell_to_contain_formula(2, 2, '=$B$1+1') def test_absolute_references_outside_cut_range_to_inside_are_rewritten(self): # * Harold logs in to dirigible, his favourite cloud computing tool self.login_and_create_new_sheet() # * Harold creates a formula in A2, referencing $A$1 self.enter_cell_text(1, 1, '1') self.enter_cell_text(1, 2, '=$A$1+1') self.wait_for_cell_to_contain_formula(1, 2, '=$A$1+1') # * Cut and pastes A1 to B1 self.cut_range((1, 1), (1, 1)) self.paste_range((2, 1)) self.wait_for_spinner_to_stop() # * He is pleased to see the formula in B2 is rewritten self.wait_for_cell_to_contain_formula(1, 2, '=$B$1+1') def test_column_absolute_references_are_partially_rewritten(self): # * Harold logs in to dirigible, his favourite cloud computing tool self.login_and_create_new_sheet() # * Harold creates a formula in A2, referencing $A1 self.enter_cell_text(1, 1, '1') self.enter_cell_text(1, 2, '=$A1+1') self.wait_for_cell_to_contain_formula(1, 2, '=$A1+1') # * Copies and pastes A2 to B3 self.copy_range((1, 2), (1, 2)) self.paste_range((2, 3)) self.wait_for_spinner_to_stop() # * He is pleased to see the formula in B3 has been rewritten to # refer to $A2 self.wait_for_cell_to_contain_formula(2, 3, '=$A2+1') def test_row_absolute_references_are_partially_rewritten(self): # * Harold logs in to dirigible, his favourite cloud computing tool self.login_and_create_new_sheet() # * Harold creates a formula in A2, referencing A$1 self.enter_cell_text(1, 1, '1') self.enter_cell_text(1, 2, '=A$1+1') self.wait_for_cell_to_contain_formula(1, 2, '=A$1+1') # * Copies and pastes A2 to B3 self.copy_range((1, 2), (1, 2)) self.paste_range((2, 3)) self.wait_for_spinner_to_stop() # * He is pleased to see the formula in B3 has been rewritten to # refer to B$1. self.wait_for_cell_to_contain_formula(2, 3, '=B$1+1') def test_cell_rewrites_that_go_off_grid_are_handled(self): # * Harold logs in to dirigible, his favourite cloud computing tool self.login_and_create_new_sheet() # * Harold creates a formula in A2, referencing A1 self.enter_cell_text(1, 1, '1') self.enter_cell_text(1, 2, '=A1+1') self.wait_for_cell_to_contain_formula(1, 2, '=A1+1') # * Copies and pastes A2 to B1 self.copy_range((1, 2), (1, 2)) self.paste_range((2, 1)) self.wait_for_spinner_to_stop() # * He is pleased to see the formula in B1 # has an error message self.wait_for_cell_to_contain_formula(2, 1, '=#Invalid!+1') def test_cellrange_rewrites_that_go_off_grid_are_handled(self): # * Harold logs in to dirigible, his favourite cloud computing tool self.login_and_create_new_sheet() # * Harold creates a formula in A3, referencing A1:A2 self.enter_cell_text(1, 1, '1') self.enter_cell_text(1, 2, '2') self.enter_cell_text(1, 3, '=sum(A1:A2)') self.wait_for_cell_to_contain_formula(1, 3, '=sum(A1:A2)') # * Copies and pastes A3 to B2 self.copy_range((1, 3), (1, 3)) self.paste_range((2, 2)) self.wait_for_spinner_to_stop() # * He is pleased to see the formula in B2 # has an error message self.wait_for_cell_to_contain_formula(2, 2, '=sum(#Invalid!:B1)') def test_cut_paste_paste_rewrites_formulae_for_2nd_paste(self): # * Harold logs in to dirigible, his favourite cloud computing tool self.login_and_create_new_sheet() # * Harold creates a formula in A1 self.enter_cell_text(1, 1, '=B1') # He pastes elsewhere, then pastes again self.cut_range((1, 1), (1, 1)) self.paste_range((1, 2)) self.paste_range((1, 3)) self.wait_for_spinner_to_stop() # * the first paste (from a cut) does not rewrite the formula self.wait_for_cell_to_contain_formula(1, 2, '=B1') # * the second paste should act like a copy from the first paste self.wait_for_cell_to_contain_formula(1, 3, '=B2') def test_copy_paste_paste_always_rewrites_formulae(self): # * Harold logs in to dirigible, his favourite cloud computing tool self.login_and_create_new_sheet() # * Harold creates a formula in A1 self.enter_cell_text(1, 1, '=B1') # He pastes elsewhere, then pastes again self.copy_range((1, 1), (1, 1)) self.paste_range((1, 2)) self.paste_range((1, 3)) self.wait_for_spinner_to_stop() # * the first paste (from a copy) does rewrite the formula self.wait_for_cell_to_contain_formula(1, 2, '=B2') # * the second paste should act like a copy from the first paste self.wait_for_cell_to_contain_formula(1, 3, '=B3') def test_buttons(self): pass # *TODO we need cut, copy and paste buttons ================================================ FILE: dirigible/fts/tests/test_2799_FillDownDuringPaste.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from functionaltest import FunctionalTest import key_codes class Test_2799_fill_down_during_paste(FunctionalTest): def wait_for_cell_to_contain_formula(self, column, row, formula): self.open_cell_for_editing(column, row) self.wait_for_cell_editor_content(formula) self.human_key_press(key_codes.ENTER) def test_fill_down_formula_with_copy_and_paste(self): # * Harold logs in to dirigible, his favourite cloud computing tool self.login_and_create_new_sheet() # * Harold populates a table full of data self.enter_cell_text(1, 1, '1') self.enter_cell_text(2, 1, '1') self.enter_cell_text(1, 2, '2') self.enter_cell_text(2, 2, '2') self.enter_cell_text(1, 3, '3') self.enter_cell_text(2, 3, '3') # * He writes a function to sum the values in a row self.enter_cell_text(3, 1, '=A1+B1') # * He uses copy & paste to 'fill down' his formula self.copy_range((3, 1), (3, 1)) self.paste_range((3, 2), (3, 3)) self.wait_for_cell_to_contain_formula(3, 2, '=A2+B2') self.wait_for_cell_to_contain_formula(3, 3, '=A3+B3') def test_fill_down_2x2_clipboard_into_3x5_selection(self): # * Harold logs in to dirigible, his favourite cloud computing tool self.login_and_create_new_sheet() # * Harold populates a table full of data self.enter_cell_text(1, 1, '=C1') self.enter_cell_text(2, 1, '=D1') self.enter_cell_text(1, 2, '=C2') self.enter_cell_text(2, 2, '=D2') # * He uses copy & paste to 'fill down' his formula self.copy_range((1, 1), (2, 2)) self.paste_range((3, 3), (5, 7)) self.wait_for_cell_to_contain_formula(3, 3, '=E3') self.wait_for_cell_to_contain_formula(4, 3, '=F3') self.wait_for_cell_to_contain_formula(5, 3, '=G3') self.wait_for_cell_to_contain_formula(3, 4, '=E4') self.wait_for_cell_to_contain_formula(4, 4, '=F4') self.wait_for_cell_to_contain_formula(5, 4, '=G4') self.wait_for_cell_to_contain_formula(3, 5, '=E5') self.wait_for_cell_to_contain_formula(4, 5, '=F5') self.wait_for_cell_to_contain_formula(5, 5, '=G5') self.wait_for_cell_to_contain_formula(3, 6, '=E6') self.wait_for_cell_to_contain_formula(4, 6, '=F6') self.wait_for_cell_to_contain_formula(5, 6, '=G6') self.wait_for_cell_to_contain_formula(3, 7, '=E7') self.wait_for_cell_to_contain_formula(4, 7, '=F7') self.wait_for_cell_to_contain_formula(5, 7, '=G7') self.wait_for_cell_to_contain_formula(3, 8, '') self.wait_for_cell_to_contain_formula(4, 8, '') self.wait_for_cell_to_contain_formula(5, 8, '') self.wait_for_cell_to_contain_formula(6, 3, '') self.wait_for_cell_to_contain_formula(6, 4, '') self.wait_for_cell_to_contain_formula(6, 5, '') self.wait_for_cell_to_contain_formula(6, 6, '') self.wait_for_cell_to_contain_formula(6, 7, '') ================================================ FILE: dirigible/fts/tests/test_2812_CutCopyPasteInEditMode.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from __future__ import with_statement try: import unittest2 as unittest except ImportError: import unittest from functionaltest import FunctionalTest, humanesque_delay import key_codes class Test_2812_CutCopyPasteInEditMode(FunctionalTest): def test_delete_key_while_editing_still_does_what_it_should(self): # * Harold logs in and creates a new sheet self.login_and_create_new_sheet() # * He enters three characters in A1 self.open_cell_for_editing(1, 1) self.human_key_press(key_codes.NUMBER_1) self.human_key_press(key_codes.NUMBER_2) self.human_key_press(key_codes.NUMBER_3) # * He hits ctrl-A with self.key_down(key_codes.CTRL): self.human_key_press(key_codes.LETTER_A) # He hits control-X with self.key_down(key_codes.CTRL): self.human_key_press(key_codes.LETTER_X) # the text is gone self.wait_for_cell_editor_content('') # He hits control-V with self.key_down(key_codes.CTRL): self.human_key_press(key_codes.LETTER_V) # His cut text returns self.wait_for_cell_editor_content('123') # * He selects everything again with self.key_down(key_codes.CTRL): self.human_key_press(key_codes.LETTER_A) # He hits control-C with self.key_down(key_codes.CTRL): self.human_key_press(key_codes.LETTER_C) # Nothing happens to his carefully-crafted text self.wait_for_cell_editor_content('123') # He hits control-V twice with self.key_down(key_codes.CTRL): self.human_key_press(key_codes.LETTER_V) self.human_key_press(key_codes.LETTER_V) # and is presented with his final masterpiece self.wait_for_cell_editor_content('123123') ================================================ FILE: dirigible/fts/tests/test_2814_PublicWorksheets.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd. # All Rights Reserved # from __future__ import with_statement from os import path import re import time from urllib2 import HTTPError from urlparse import urljoin from functionaltest import FunctionalTest, PAGE_LOAD_TIMEOUT, SERVER_IP import key_codes class Test_2814_PublicWorksheets(FunctionalTest): user_count = 3 email_address = None def tearDown(self): FunctionalTest.tearDown(self) if self.email_address: self.clear_email_for_address(self.email_address) def waitForButtonToIndicateSheetIsPublic(self, public): if public: expected_text = 'Sheet is public' else: expected_text = 'Sheet is private' def get_button_title(): return self.selenium.get_attribute('css=#id_security_button@title') self.wait_for( lambda : expected_text in get_button_title(), lambda : "%r to be in %r" % (expected_text, get_button_title()) ) def get_button_alt_text(): return self.selenium.get_attribute('css=#id_security_button@alt') self.wait_for( lambda : expected_text in get_button_alt_text(), lambda : "%r to be in %r" % (expected_text, get_button_alt_text()) ) def test_public_worksheets_visible_readonly_and_copiable_for_others(self): # * Harold logs in and creates a new sheet sheet_id = self.login_and_create_new_sheet() # * He gives the sheet a catchy name self.set_sheet_name('spaceshuttle') # * He enters some formulae n stuff self.enter_cell_text(2, 3, '23') self.enter_cell_text(2, 4, '=my_add_function(B3)') self.prepend_usercode('my_add_function = lambda x : x + 2') self.wait_for_cell_value(2, 4, '25') # * He notes that the tooltip for the security icon indicates that the # sheet is private self.waitForButtonToIndicateSheetIsPublic(False) # * He clicks on the security icon self.selenium.click('id=id_security_button') # He sees a tickbox, currently unticked, saying make worksheet public self.wait_for_element_visibility( 'id=id_security_form', True) self.wait_for_element_visibility( 'id=id_security_form_public_sheet_checkbox', True) self.assertEquals( self.selenium.get_value('id=id_security_form_public_sheet_checkbox'), 'off' ) # He ticks it and dismisses the dialog self.selenium.click('id=id_security_form_public_sheet_checkbox') self.selenium.click('id=id_security_form_ok_button') # * He notes that the tooltip for the security icon indicates that the # sheet is public self.waitForButtonToIndicateSheetIsPublic(True) # He notes down the URL and emails it to his colleague Harriet harolds_url = self.browser.current_url # He logs out self.logout() # * Later on, Harriet logs into teh Dirigible and heads on over to # Harold's spreadsheet self.login(self.get_my_usernames()[1]) self.go_to_url(harolds_url) # She sees the values n stuff self.wait_for_grid_to_appear() self.wait_for_cell_value(2, 4, '25') # * She notices that all toolbar icons are missing, # apart from download-as-csv map( lambda e: self.wait_for_element_presence(e, False), [ 'id=id_import_button', 'id=id_cut_button', 'id=id_copy_button', 'id=id_paste_button', 'id=id_security_button', ] ) self.wait_for_element_visibility('id=id_export_button', True) # * She tries to edit some formulae, but can't self.selenium.double_click( self.get_cell_locator(1, 1) ) self.selenium.focus( self.get_cell_locator(1, 1) ) time.sleep(1) self.wait_for_element_presence( self.get_active_cell_editor_locator(), False ) # * she tries to edit the cell again, using the formula bar, but cannot self.assertEquals( self.selenium.get_attribute(self.get_formula_bar_locator() + '@readonly'), 'true' ) # * She tries to edit some usercode, but can't original_code = self.get_usercode() self.selenium.get_eval('window.editor.focus()') self.human_key_press(key_codes.LETTER_A) time.sleep(1) self.wait_for_usercode_editor_content(original_code) # * She tries to edit the sheet name, but can't # * mouses over the sheet name and notes that the appearance # does not change to indicate that it's editable self.selenium.mouse_over('id=id_sheet_name') time.sleep(1) self.wait_for( lambda: self.get_css_property('#id_sheet_name', 'background-color') == 'transparent', lambda: 'ensure sheet name background stays normal') # * He clicks on the sheet name, the sheetname edit textarea does # not appear, self.selenium.click('id=id_sheet_name') time.sleep(1) self.wait_for( lambda: not self.is_element_present('id=edit-id_sheet_name'), lambda: 'ensure editable sheetname does not appear') def download_as_csv(): self.selenium.click('id=id_export_button') self.wait_for_element_visibility('id=id_export_dialog', True) download_url = self.selenium.get_attribute('id=id_export_csv_excel_version@href') download_url = urljoin(self.browser.current_url, download_url) stream = self.get_url_with_session_cookie(download_url) self.assertEquals(stream.info().gettype(), "text/csv") self.assertEquals( stream.info()['Content-Disposition'], 'attachment; filename=spaceshuttle.csv' ) expected_file_name = path.join( path.dirname(__file__), "test_data", "public_sheet_csv_file.csv" ) with open(expected_file_name) as expected_file: self.assertEquals( stream.read().replace("\r\n", "\n"), expected_file.read().replace("\r\n", "\n") ) # * She confirms that she can download a csv of the sheet download_as_csv() # * She uses some l33t haxx0ring skillz to try and send a # setcellformula Ajax call directly # It doesn't work. with self.assertRaises(HTTPError): response = self.get_url_with_session_cookie( urljoin(harolds_url, '/set_cell_formula/'), data={'column':3, 'row': 4, 'formula': '=jeffk'} ) # * "Aha!" she says, as she notices a link allowing her to copy the sheet, self.wait_for_element_visibility('id_copy_sheet_link', True) # which she then clicks self.selenium.click('id=id_copy_sheet_link') # She is taken to a sheet of her own self.selenium.wait_for_page_to_load(PAGE_LOAD_TIMEOUT) self.wait_for_grid_to_appear() # It looks a lot like Harold's but has a different url harriets_url = self.browser.current_url self.assertFalse(harriets_url == harolds_url) self.wait_for_cell_value(2, 4, '25') # And she is able to change cell formulae self.enter_cell_text(2, 3, '123') self.wait_for_cell_value(2, 4, '125') # And she is able to change usercode self.append_usercode('worksheet[2, 4].value += 100') self.wait_for_cell_value(2, 4, '225') # And she is well pleased. So much so that she emails two # friends about these two sheets (and they tell two # friends, and they tell two friends, and so on, and so # on. $$$$) self.logout() # * Helga is a Dirigible user, but she isn't logged in. # She goes to Harold's page, and sees that it is good. self.go_to_url(harolds_url) self.wait_for_grid_to_appear() self.wait_for_cell_value(2, 4, '25') # She clicks on the big copy button, and is taken to the # login form self.selenium.click('id=id_copy_sheet_link') self.selenium.wait_for_page_to_load(PAGE_LOAD_TIMEOUT) self.wait_for_element_visibility('id_login_form_wrap', True) # She logs in, and is taken straight to her new copy of # Harold's sheet self.login( self.get_my_usernames()[2], already_on_login_page=True ) self.wait_for_grid_to_appear() helgas_url = self.browser.current_url self.assertFalse(helgas_url == harolds_url) self.assertFalse(helgas_url == harriets_url) self.wait_for_cell_value(2, 4, '25') # Helga makes some edits, which she considers superior to # Harriet's self.enter_cell_text(2, 3, '1000') self.append_usercode('worksheet[2, 4].value += 1000') self.wait_for_cell_value(2, 4, '2002') # Helga now decides to go and see Harriet's sheet, to # laugh at the inferiority of Harriet's fork # Her access is denied. self.assert_HTTP_error(harriets_url, 403) # * Harriet's other friend, Hugh, is not a Dirigible user.... yet. # He goes to Harold's sheet and sees that it is good self.logout() self.go_to_url(harolds_url) self.wait_for_grid_to_appear() self.wait_for_cell_value(2, 4, '25') # So good that he clicks the copy button too, despite never # having heard of this Dirigible thingy self.selenium.click('id=id_copy_sheet_link') self.selenium.wait_for_page_to_load(PAGE_LOAD_TIMEOUT) # He is taken to the login form, self.wait_for_element_visibility('id_login_form_wrap', True) # on which he spots a nice friendly link inviting him to register. # It says 'free' and everyfink. self.wait_for_element_to_appear('id=id_login_signup_link') self.wait_for_element_to_appear('id=id_login_signup_blurb') self.assertTrue("free" in self.get_text('id=id_login_signup_blurb')) # Hugh goes through the whole registration rigmarole, self.selenium.click('id=id_login_signup_link') self.selenium.wait_for_page_to_load(PAGE_LOAD_TIMEOUT) username = self.get_my_username() + "_x" self.email_address = 'harold.testuser-%s@resolversystems.com' % (username,) password = "p4ssw0rd" self.selenium.type('id=id_username', username) self.selenium.type('id=id_email', self.email_address) self.selenium.type('id=id_password1', password) self.selenium.type('id=id_password2', password) self.click_link('id_signup_button') email_from, email_to, subject, message = self.pop_email_for_client(self.email_address) self.assertEquals(subject, 'Dirigible Beta Sign-up') confirm_url_re = re.compile( r'<(http://projectdirigible\.com/signup/activate/[^>]+)>' ) match = confirm_url_re.search(message) self.assertTrue(match) confirmation_url = match.group(1).replace('projectdirigible.com', SERVER_IP) # * Hugh then logs in self.go_to_url(confirmation_url) self.login(username, password, already_on_login_page=True) # and has his socks knocked off by the presence of the copy of Harold's # sheet in his dashboard self.selenium.click('link=spaceshuttle') # and it has the copied content self.selenium.wait_for_page_to_load(PAGE_LOAD_TIMEOUT) self.wait_for_grid_to_appear() self.wait_for_cell_value(2, 4, '25') # Harold logs in and sees that his original sheet is unharmed by all of # the other users editing theirs self.login(self.get_my_usernames()[0]) self.go_to_url(harolds_url) self.wait_for_grid_to_appear() self.wait_for_cell_value(2, 4, '25') def test_admin_can_copy_non_public_sheets(self): # * Harold logs in to Dirigible and creates a new sheet sheet_id = self.login_and_create_new_sheet() self.enter_cell_text(1, 1, '=6 + 11') # * He is having some trouble working with a complicated task # so he asks an admin for help. Giving him the sheet url sheet_url = '/user/%s/sheet/%s' % (self.get_my_username(), sheet_id) self.logout() # * The admin logs in and visits the sheet self.login('admin', ' {% endblock %} {% block body %}
{% include "header_links_include.html" %}
Dirigible is a programmable cloud spreadsheet.
href='{% url 'info_page' "video" %}'>Watch a video
Sign up now!
{% include "footer_links_include.html" %}
{% endblock %} ================================================ FILE: dirigible/info_pages/templates/oss.html ================================================ {% extends "info_page.html" %} {% block title %} Thanks to Open Source Projects: Dirigible {% endblock %} {% block head %} {{ block.super }} {% endblock %} {% block middle %}

Thanks!

It would have been much harder to create Dirigible without a bunch of great Open Source projects, and we'd like to take this opportunity to say "thanks!"

The most visible projects we use are:

  • Michael Leibman's excellent SlickGrid, an essential part of a programmable cloud spreadsheet.
  • Almost as important for the programmable part is the Ajax.org Cloud9 Editor, aka ACE.
  • Dirigible is, of course, programmable in Python.

There are tons of great Open Source libraries for Python, and we've made sure that some of the best are pre-installed:

  • NumPy, the fundamental package needed for scientific computing with Python.
  • GmPy, provides multiprecision arithmetic
  • MpMath, another library for multiprecision floating-point arithmetic.
  • The SciPy library, providing scientific tools for mathematics, science, and engineering.
  • PyCrypto, the Python cryptography toolkit.
  • SqlAlchemy, a Python SQL toolkit and object relational mapper.
  • LXML, for working with XML and HTML.
  • xlrd, for reading Excel files.
  • RDFLib, for working with RDF.
  • GeoPy, a geocoding toolbox.
  • Beautiful Soup, a forgiving HTML parser for real-world web pages.
  • Mechanize, stateful programmatic web browsing.

Less directly visible to users, our server-side stack includes many open source components, including:

  • Debian the GNU/Linux operating system on which it all runs.
  • Apache, the world's most popular web-server.
  • Modwsgi for Apache, connecting our web server to Python.
  • Django a Python web framework.
  • Django-registration, making it easy for users to sign up to Dirigible.
  • setuptools, The Python library to install Python libraries.

Finally, back at the coal-face, our programmers use many development tools and libraries, the most prominent of which are:

  • Unittest2 and Mock, indispensible Python testing libraries by eminent Resolver Systems alumnus Michael Foord.
  • Ply, by David Beazley, allows us to parse our customised Python grammer for cell formulae.
  • Chardet, by Mark Pilgrim, is a godsend for discerning unicode encodings.
{% endblock %} ================================================ FILE: dirigible/info_pages/templates/video.html ================================================ {% extends "non_sheet_page_small_logo.html" %} {% block title %} The Video: Dirigible {% endblock %} {% block head %} {{ block.super }} {% endblock %} {% block content %}
{% endblock %} ================================================ FILE: dirigible/info_pages/tests/__init__.py ================================================ ================================================ FILE: dirigible/info_pages/tests/test_views.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd, PythonAnywhere LLP # See LICENSE.md # from mock import patch, sentinel, Mock from dirigible.test_utils import ResolverTestCase from info_pages.views import front_page_view, info_page_view, non_logged_in_front_page_view class TestFrontPageView(ResolverTestCase): @patch('info_pages.views.non_logged_in_front_page_view') def test_should_delegate_to_non_logged_in_front_page_view_if_not_logged_in(self, mock_non_logged_in_front_page_view): request = Mock() request.user.is_authenticated.return_value = False response = front_page_view(request) self.assertCalledOnce(mock_non_logged_in_front_page_view, request) self.assertEquals(response, mock_non_logged_in_front_page_view.return_value) @patch('info_pages.views.user_dashboard') def test_should_delegate_to_user_dashboard_if_are_logged_in(self, mock_user_dashboard): request = Mock() request.user.is_authenticated.return_value = True response = front_page_view(request) self.assertCalledOnce(mock_user_dashboard, request) self.assertEquals(response, mock_user_dashboard.return_value) class TestNonLoggedInFrontPageView(ResolverTestCase): @patch('info_pages.views.render_to_response') def test_should_render_template_to_response_with_registration_form(self, mock_render_to_response): response = non_logged_in_front_page_view(sentinel.request) self.assertCalledOnce( mock_render_to_response, 'non_logged_in_front_page.html', {} ) self.assertEquals(response, mock_render_to_response.return_value) class TestInfoPageView(ResolverTestCase): @patch('info_pages.views.render_to_response') def test_should_render_template_to_response_with_user(self, mock_render_to_response): request = Mock() request.user.is_authenticated.return_value = False response = info_page_view(request, "my_template_name") self.assertCalledOnce( mock_render_to_response, 'my_template_name.html', { 'user': request.user } ) self.assertEquals(response, mock_render_to_response.return_value) ================================================ FILE: dirigible/info_pages/views.py ================================================ # Copyright (c) 2010 Resolver Systems Ltd, PythonAnywhere LLP # See LICENSE.md # from django.shortcuts import render_to_response from user.views import user_dashboard def front_page_view(request): if request.user.is_authenticated(): return user_dashboard(request) else: return non_logged_in_front_page_view(request) def non_logged_in_front_page_view(request): return render_to_response('non_logged_in_front_page.html', {}) def info_page_view(request, template_name): return render_to_response( '%s.html' % (template_name,), {'user': request.user} ) ================================================ FILE: dirigible/manage.py ================================================ #!/usr/bin/env python import os import sys if __name__ == "__main__": os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dirigible.settings") from django.core.management import execute_from_command_line execute_from_command_line(sys.argv) ================================================ FILE: dirigible/registration/__init__.py ================================================ ================================================ FILE: dirigible/registration/admin.py ================================================ from django.contrib import admin from registration.models import RegistrationProfile class RegistrationAdmin(admin.ModelAdmin): list_display = ('__unicode__', 'activation_key_expired') search_fields = ('user__username', 'user__first_name') admin.site.register(RegistrationProfile, RegistrationAdmin) ================================================ FILE: dirigible/registration/forms.py ================================================ """ Forms and validation code for user registration. """ from django import forms from django.utils.translation import ugettext_lazy as _ from django.contrib.auth.models import User from registration.models import RegistrationProfile # I put this on all required fields, because it's easier to pick up # on them with CSS or JavaScript if they have a class of "required" # in the HTML. Your mileage may vary. If/when Django ticket #3515 # lands in trunk, this will no longer be necessary. attrs_dict = { 'class': 'required' } class RegistrationForm(forms.Form): """ Form for registering a new user account. Validates that the requested username is not already in use, and requires the password to be entered twice to catch typos. Subclasses should feel free to add any additional validation they need, but should either preserve the base ``save()`` or implement a ``save()`` which accepts the ``profile_callback`` keyword argument and passes it through to ``RegistrationProfile.objects.create_inactive_user()``. """ username = forms.RegexField(regex=r'^\w+$', max_length=30, widget=forms.TextInput(attrs=attrs_dict), label=_(u'username')) email = forms.EmailField(widget=forms.TextInput(attrs=dict(attrs_dict, maxlength=75)), label=_(u'email address')) password1 = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict, render_value=False), label=_(u'password')) password2 = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict, render_value=False), label=_(u'password (again)')) def clean_username(self): """ Validate that the username is alphanumeric and is not already in use. """ try: user = User.objects.get(username__iexact=self.cleaned_data['username']) except User.DoesNotExist: return self.cleaned_data['username'] raise forms.ValidationError(_(u'This username is already taken. Please choose another.')) def clean(self): """ Verifiy that the values entered into the two password fields match. Note that an error here will end up in ``non_field_errors()`` because it doesn't apply to a single field. """ if 'password1' in self.cleaned_data and 'password2' in self.cleaned_data: if self.cleaned_data['password1'] != self.cleaned_data['password2']: raise forms.ValidationError(_(u'You must type the same password each time')) return self.cleaned_data def save(self, profile_callback=None): """ Create the new ``User`` and ``RegistrationProfile``, and returns the ``User``. This is essentially a light wrapper around ``RegistrationProfile.objects.create_inactive_user()``, feeding it the form data and a profile callback (see the documentation on ``create_inactive_user()`` for details) if supplied. """ new_user = RegistrationProfile.objects.create_inactive_user(username=self.cleaned_data['username'], password=self.cleaned_data['password1'], email=self.cleaned_data['email'], profile_callback=profile_callback) return new_user class RegistrationFormTermsOfService(RegistrationForm): """ Subclass of ``RegistrationForm`` which adds a required checkbox for agreeing to a site's Terms of Service. """ tos = forms.BooleanField(widget=forms.CheckboxInput(attrs=attrs_dict), label=_(u'I have read and agree to the Terms of Service'), error_messages={ 'required': u"You must agree to the terms to register" }) class RegistrationFormUniqueEmail(RegistrationForm): """ Subclass of ``RegistrationForm`` which enforces uniqueness of email addresses. """ def clean_email(self): """ Validate that the supplied email address is unique for the site. """ if User.objects.filter(email__iexact=self.cleaned_data['email']): raise forms.ValidationError(_(u'This email address is already in use. Please supply a different email address.')) return self.cleaned_data['email'] class RegistrationFormNoFreeEmail(RegistrationForm): """ Subclass of ``RegistrationForm`` which disallows registration with email addresses from popular free webmail services; moderately useful for preventing automated spam registrations. To change the list of banned domains, subclass this form and override the attribute ``bad_domains``. """ bad_domains = ['aim.com', 'aol.com', 'email.com', 'gmail.com', 'googlemail.com', 'hotmail.com', 'hushmail.com', 'msn.com', 'mail.ru', 'mailinator.com', 'live.com'] def clean_email(self): """ Check the supplied email address against a list of known free webmail domains. """ email_domain = self.cleaned_data['email'].split('@')[1] if email_domain in self.bad_domains: raise forms.ValidationError(_(u'Registration using free email addresses is prohibited. Please supply a different email address.')) return self.cleaned_data['email'] ================================================ FILE: dirigible/registration/locale/ar/LC_MESSAGES/django.po ================================================ # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2007-09-19 19:30-0500\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: forms.py:38 msgid "username" msgstr "اسم المستخدم" #: forms.py:41 msgid "email address" msgstr "عنوان البريد الالكتروني" #: forms.py:43 msgid "password" msgstr "كلمة المرور" #: forms.py:45 msgid "password (again)" msgstr "تأكيد كلمة المرور" #: forms.py:54 msgid "Usernames can only contain letters, numbers and underscores" msgstr "يمكن أن يحتوي اسم المستخدم على احرف، ارقام وشرطات سطرية فقط" #: forms.py:59 msgid "This username is already taken. Please choose another." msgstr "اسم المستخدم مسجل مسبقا. يرجى اختيار اسم اخر." #: forms.py:68 msgid "You must type the same password each time" msgstr "يجب ادخال كلمة المرور مطابقة كل مرة" #: forms.py:96 msgid "I have read and agree to the Terms of Service" msgstr "أقر بقراءة والموافقة على شروط الخدمة" #: forms.py:105 msgid "You must agree to the terms to register" msgstr "يجب الموافقة على الشروط للتسجيل" #: forms.py:124 msgid "" "This email address is already in use. Please supply a different email " "address." msgstr "عنوان البريد الالكتروني مسجل مسبقا. يرجى تزويد عنوان بريد الكتروني مختلف." #: forms.py:149 msgid "" "Registration using free email addresses is prohibited. Please supply a " "different email address." msgstr "يمنع التسجيل باستخدام عناوين بريد الكترونية مجانية. يرجى تزويد عنوان بريد الكتروني مختلف." #: models.py:188 msgid "user" msgstr "مستخدم" #: models.py:189 msgid "activation key" msgstr "رمز التفعيل" #: models.py:194 msgid "registration profile" msgstr "ملف التسجيل الشخصي" #: models.py:195 msgid "registration profiles" msgstr "ملفات التسجيل الشخصية" ================================================ FILE: dirigible/registration/locale/bg/LC_MESSAGES/django.po ================================================ # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2007-09-19 19:30-0500\n" "PO-Revision-Date: 2008-03-05 12:37+0200\n" "Last-Translator: Vladislav \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Poedit-Bookmarks: -1,-1,-1,-1,10,-1,-1,-1,-1,-1\n" #: forms.py:38 msgid "username" msgstr "Потребителско име " #: forms.py:41 msgid "email address" msgstr "Електронна поща" #: forms.py:43 msgid "password" msgstr "Парола" #: forms.py:45 msgid "password (again)" msgstr "Парола (проверка)" #: forms.py:54 msgid "Usernames can only contain letters, numbers and underscores" msgstr "Потребителските имена могат да съдържат букви, цифри и подчертавки" #: forms.py:59 msgid "This username is already taken. Please choose another." msgstr "Потребителското име е заето. Моля изберето друго." #: forms.py:68 msgid "You must type the same password each time" msgstr "Грешка при проверка на паролата." #: forms.py:96 msgid "I have read and agree to the Terms of Service" msgstr "Прочел съм и съм съгласен с условията за експлоатация" #: forms.py:105 msgid "You must agree to the terms to register" msgstr "Трябва да сте съгласни с условията за да се регистрирате." #: forms.py:124 msgid "This email address is already in use. Please supply a different email address." msgstr "Адреса на електронната поща е използван. Моля въведете друг адрес." #: forms.py:149 msgid "Registration using free email addresses is prohibited. Please supply a different email address." msgstr "Регистрациите с безплатни адреси е забранен. Моля въведете различен адрес за електронна поща" #: models.py:188 msgid "user" msgstr "Потребител" #: models.py:189 msgid "activation key" msgstr "Ключ за активация" #: models.py:194 msgid "registration profile" msgstr "регистрационен профил" #: models.py:195 msgid "registration profiles" msgstr "регистрационни профили" ================================================ FILE: dirigible/registration/locale/de/LC_MESSAGES/django.po ================================================ # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # Jannis Leidel , 2007. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: django-registration 0.3 \n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2007-09-19 19:30-0500\n" "PO-Revision-Date: 2007-09-29 16:50+0200\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Deutsch \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: forms.py:38 msgid "username" msgstr "Benutzername" #: forms.py:41 msgid "email address" msgstr "E-Mail-Adresse" #: forms.py:43 msgid "password" msgstr "Passwort" #: forms.py:45 msgid "password (again)" msgstr "Passwort (wiederholen)" #: forms.py:54 msgid "Usernames can only contain letters, numbers and underscores" msgstr "Benutzernamen können nur Buchstaben, Zahlen und Unterstriche enthalten" #: forms.py:59 msgid "This username is already taken. Please choose another." msgstr "Dieser Benutzername ist schon vergeben. Bitte einen anderen wählen." #: forms.py:68 msgid "You must type the same password each time" msgstr "Bitte das gleiche Passwort zur Überprüfung nochmal eingeben" #: forms.py:96 msgid "I have read and agree to the Terms of Service" msgstr "Ich habe die Nutzungsvereinbarung gelesen und stimme ihr zu" #: forms.py:105 msgid "You must agree to the terms to register" msgstr "Sie müssen der Nutzungsvereinbarung zustimmen, um sich zu registrieren" #: forms.py:124 msgid "" "This email address is already in use. Please supply a different email " "address." msgstr "" "Diese E-Mail-Adresse wird schon genutzt. Bitte geben Sie eine andere " "E-Mail-Adresse an." #: forms.py:149 msgid "" "Registration using free email addresses is prohibited. Please supply a " "different email address." msgstr "" "Die Registrierung mit einer kostenlosen E-Mail-Adresse ist untersagt. Bitte " "geben Sie eine andere E-Mail-Adresse an." #: models.py:188 msgid "user" msgstr "Benutzer" #: models.py:189 msgid "activation key" msgstr "Aktivierungsschlüssel" #: models.py:194 msgid "registration profile" msgstr "Registrierungsprofil" #: models.py:195 msgid "registration profiles" msgstr "Registrierungsprofile" ================================================ FILE: dirigible/registration/locale/el/LC_MESSAGES/django.po ================================================ # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # Panos Laganakos , 2007. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2007-09-19 19:30-0500\n" "PO-Revision-Date: 2007-11-14 21:50+0200\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: forms.py:38 msgid "username" msgstr "όνομα χρήστη" #: forms.py:41 msgid "email address" msgstr "διεύθυνση ηλεκτρονικού ταχυδρομείου" #: forms.py:43 msgid "password" msgstr "συνθηματικό" #: forms.py:45 msgid "password (again)" msgstr "συνθηματικό (ξανά)" #: forms.py:54 msgid "Usernames can only contain letters, numbers and underscores" msgstr "Τα ονόματα χρηστών μπορούν να περιλαμβάνουν μόνο γράμματα, αριθμούς και υπογραμμίσεις" #: forms.py:59 msgid "This username is already taken. Please choose another." msgstr "Αυτό το όνομα χρήστη χρησιμοποίειται ήδη. Παρακαλώ διαλέξτε ένα άλλο." #: forms.py:68 msgid "You must type the same password each time" msgstr "Πρέπει να εισάγετε το ίδιο συνθηματικό κάθε φορά" #: forms.py:96 msgid "I have read and agree to the Terms of Service" msgstr "Διάβασα και συμφωνώ με τους Όρους της Υπηρεσίας" #: forms.py:105 msgid "You must agree to the terms to register" msgstr "Πρέπει να συμφωνείται με τους όρους για να εγγραφείτε" #: forms.py:124 msgid "" "This email address is already in use. Please supply a different email " "address." msgstr "" "Η συγκεκριμένη διεύθυνση ηλεκτρονικού ταχυδρομείου χρησιμοποιείται ήδη. " "Παρακαλώ δώστε κάποια άλλη." #: forms.py:149 msgid "" "Registration using free email addresses is prohibited. Please supply a " "different email address." msgstr "" "Η εγγραφή μέσω δωρεάν διευθύνσεων ηλεκτρονικού ταχυδρομείου απαγορεύεται. ""Παρακαλώ δώστε κάποια άλλη." #: models.py:188 msgid "user" msgstr "χρήστης" #: models.py:189 msgid "activation key" msgstr "κλειδί ενεργοποίησης" #: models.py:194 msgid "registration profile" msgstr "προφίλ εγγραφής" #: models.py:195 msgid "registration profiles" msgstr "προφίλ εγγραφών" ================================================ FILE: dirigible/registration/locale/en/LC_MESSAGES/django.po ================================================ # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2007-09-19 19:30-0500\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: forms.py:38 msgid "username" msgstr "" #: forms.py:41 msgid "email address" msgstr "" #: forms.py:43 msgid "password" msgstr "" #: forms.py:45 msgid "password (again)" msgstr "" #: forms.py:54 msgid "Usernames can only contain letters, numbers and underscores" msgstr "" #: forms.py:59 msgid "This username is already taken. Please choose another." msgstr "" #: forms.py:68 msgid "You must type the same password each time" msgstr "" #: forms.py:96 msgid "I have read and agree to the Terms of Service" msgstr "" #: forms.py:105 msgid "You must agree to the terms to register" msgstr "" #: forms.py:124 msgid "" "This email address is already in use. Please supply a different email " "address." msgstr "" #: forms.py:149 msgid "" "Registration using free email addresses is prohibited. Please supply a " "different email address." msgstr "" #: models.py:188 msgid "user" msgstr "" #: models.py:189 msgid "activation key" msgstr "" #: models.py:194 msgid "registration profile" msgstr "" #: models.py:195 msgid "registration profiles" msgstr "" ================================================ FILE: dirigible/registration/locale/es/LC_MESSAGES/django.po ================================================ # Spanish translation for django-registration. # Copyright (C) 2007, James Bennet # This file is distributed under the same license as the registration package. # Ernesto Rico Schmidt , 2008. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: django-registration 0.3 \n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2008-03-11 00:19-0400\n" "PO-Revision-Date: 2008-03-11 00:19-0400\n" "Last-Translator: Ernesto Rico Schmidt \n" "Language-Team: Español \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: forms.py:38 msgid "username" msgstr "nombre de usuario" #: forms.py:41 msgid "email address" msgstr "dirección de coreo electrónico" #: forms.py:43 msgid "password" msgstr "contraseña" #: forms.py:45 msgid "password (again)" msgstr "contraseña (otra vez)" #: forms.py:54 msgid "Usernames can only contain letters, numbers and underscores" msgstr "Los nombres de usuarios sólo pueden contener letras, números y guiones bajos" #: forms.py:59 msgid "This username is already taken. Please choose another." msgstr "Este nombre de usuario ya está ocupado. Por favor escoge otro" #: forms.py:71 msgid "You must type the same password each time" msgstr "Tienes que introducir la misma contraseña cada vez" #: forms.py:100 msgid "I have read and agree to the Terms of Service" msgstr "He leído y acepto los términos de servicio" #: forms.py:109 msgid "You must agree to the terms to register" msgstr "Tienes que aceptar los términos para registrarte" #: forms.py:128 msgid "" "This email address is already in use. Please supply a different email " "address." msgstr "" "La dirección de correo electrónico ya está siendo usada. Por favor" "proporciona otra dirección." #: forms.py:153 msgid "" "Registration using free email addresses is prohibited. Please supply a " "different email address." msgstr "" "El registro usando una dirección de correo electrónico gratis está prohibido." "Por favor proporciona otra dirección." #: models.py:188 msgid "user" msgstr "usuario" #: models.py:189 msgid "activation key" msgstr "clave de activación" #: models.py:194 msgid "registration profile" msgstr "perfil de registro" #: models.py:195 msgid "registration profiles" msgstr "perfiles de registro" ================================================ FILE: dirigible/registration/locale/es_AR/LC_MESSAGES/django.po ================================================ # SOME DESCRIPTIVE TITLE. # Copyright (C) 2008 Leonardo Manuel Rocha # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2007-09-19 19:30-0500\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: forms.py:38 msgid "username" msgstr "nombre de usuario" #: forms.py:41 msgid "email address" msgstr "dirección de e-mail" #: forms.py:43 msgid "password" msgstr "contraseña" #: forms.py:45 msgid "password (again)" msgstr "contraseña (nuevamente)" #: forms.py:54 msgid "Usernames can only contain letters, numbers and underscores" msgstr "El nombre de usuario solo puede contener letras, números y guiones bajos" #: forms.py:59 msgid "This username is already taken. Please choose another." msgstr "Ese nombre de usuario ya está asignado. Por favor elija otro." #: forms.py:68 msgid "You must type the same password each time" msgstr "Debe tipear la misma contraseña cada vez" #: forms.py:96 msgid "I have read and agree to the Terms of Service" msgstr "He leído y estoy de acuerdo con las Condiciones de Servicio" #: forms.py:105 msgid "You must agree to the terms to register" msgstr "Debe estar de acuerdo con las Condiciones para poder registrarse" #: forms.py:124 msgid "" "This email address is already in use. Please supply a different email " "address." msgstr "Esa dirección de e-mail ya está en uso. Por favor provea otra " "dirección." #: forms.py:149 msgid "" "Registration using free email addresses is prohibited. Please supply a " "different email address." msgstr "La registración con un e-mail gratuito está prohibida. Por favor " "de una dirección de e-mail diferente." #: models.py:188 msgid "user" msgstr "usuario" #: models.py:189 msgid "activation key" msgstr "clave de activación" #: models.py:194 msgid "registration profile" msgstr "perfil de registro" #: models.py:195 msgid "registration profiles" msgstr "perfiles de registro" ================================================ FILE: dirigible/registration/locale/fr/LC_MESSAGES/django.po ================================================ # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # Samuel Adam , 2007. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: django-registration 0.3 \n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2007-09-19 19:30-0500\n" "PO-Revision-Date: 2007-09-20 10:30+0100\n" "Last-Translator: Samuel Adam \n" "Language-Team: Français \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: forms.py:38 msgid "username" msgstr "pseudo" #: forms.py:41 msgid "email address" msgstr "adresse email" #: forms.py:43 msgid "password" msgstr "mot de passe" #: forms.py:45 msgid "password (again)" msgstr "mot de passe (vérification)" #: forms.py:54 msgid "Usernames can only contain letters, numbers and underscores" msgstr "Le pseudo ne peut contenir que des lettres, chiffres et le caractère souligné." #: forms.py:59 msgid "This username is already taken. Please choose another." msgstr "Ce pseudo est déjà utilisé. Veuillez en choisir un autre." #: forms.py:68 msgid "You must type the same password each time" msgstr "Veuillez indiquer le même mot de passe dans les deux champs" #: forms.py:96 msgid "I have read and agree to the Terms of Service" msgstr "J'ai lu et accepté les Conditions Générales d'Utilisation" #: forms.py:105 msgid "You must agree to the terms to register" msgstr "Vous devez accepter les conditions d'utilisation pour vous inscrire" #: forms.py:124 msgid "" "This email address is already in use. Please supply a different email " "address." msgstr "Cette adresse email est déjà utilisée. Veuillez en indiquer une autre." #: forms.py:149 msgid "" "Registration using free email addresses is prohibited. Please supply a " "different email address." msgstr "L'inscription avec une adresse email d'un compte gratuit est interdite. Veuillez en indiquer une autre." #: models.py:188 msgid "user" msgstr "utilisateur" #: models.py:189 msgid "activation key" msgstr "clé d'activation" #: models.py:194 msgid "registration profile" msgstr "profil d'inscription" #: models.py:195 msgid "registration profiles" msgstr "profils d'inscription" ================================================ FILE: dirigible/registration/locale/he/LC_MESSAGES/django.po ================================================ # translation of registration. # Copyright (C) 2008 THE registration'S COPYRIGHT HOLDER # This file is distributed under the same license as the registration package. # <>, 2008. # , fuzzy # <>, 2008. # # msgid "" msgstr "" "Project-Id-Version: registration\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2008-02-10 02:01+0200\n" "PO-Revision-Date: 2008-02-10 02:05+0200\n" "Last-Translator: Meir Kriheli \n" "Language-Team: Hebrew\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit" #: forms.py:38 msgid "username" msgstr "שם משתמש" #: forms.py:41 msgid "email address" msgstr "דואר אלקטרוני" #: forms.py:43 msgid "password" msgstr "סיסמה" #: forms.py:45 msgid "password (again)" msgstr "סיסמה (שוב)" #: forms.py:54 msgid "Usernames can only contain letters, numbers and underscores" msgstr "שמות משתמש יכולים להכיל רק אותיות, ספרות וקווים תחתונים" #: forms.py:59 msgid "This username is already taken. Please choose another." msgstr "שם המשתמש תפוס כבר. נא לבחור אחר." #: forms.py:64 msgid "You must type the same password each time" msgstr "יש להקליד את אותה הסיסמה פעמיים" #: forms.py:93 msgid "I have read and agree to the Terms of Service" msgstr "קראתי והסכמתי לתנאי השימוש" #: forms.py:102 msgid "You must agree to the terms to register" msgstr "עליך להסכים לתנאי השימוש" #: forms.py:121 msgid "" "This email address is already in use. Please supply a different email " "address." msgstr "" "כתובת הדואר האלקטרוני תפוסה כבר. נא לספק כתובת דואר אחרת." #: forms.py:146 msgid "" "Registration using free email addresses is prohibited. Please supply a " "different email address." msgstr "" "הרישום בעזרת תיבת דואר אלקטרוני חינמית אסור. נא לספק כתובת אחרת." #: models.py:188 msgid "user" msgstr "משתמש" #: models.py:189 msgid "activation key" msgstr "מפתח הפעלה" #: models.py:194 msgid "registration profile" msgstr "פרופיל רישום" #: models.py:195 msgid "registration profiles" msgstr "פרופילי רישום" ================================================ FILE: dirigible/registration/locale/it/LC_MESSAGES/django.po ================================================ # translation of django.po to Italiano # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # # Nicola Larosa , 2008. msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2007-09-19 19:30-0500\n" "PO-Revision-Date: 2008-05-27 15:05+0200\n" "Last-Translator: Nicola Larosa \n" "Language-Team: Italiano\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: KBabel 1.11.4\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: forms.py:38 msgid "username" msgstr "nome utente" #: forms.py:41 msgid "email address" msgstr "indirizzo email" #: forms.py:43 msgid "password" msgstr "password" #: forms.py:45 msgid "password (again)" msgstr "password (di nuovo)" #: forms.py:54 msgid "Usernames can only contain letters, numbers and underscores" msgstr "I nomi utente possono contenere solo lettere, numeri e sottolineature" #: forms.py:59 msgid "This username is already taken. Please choose another." msgstr "Questo nome utente è già usato. Scegline un altro." #: forms.py:68 msgid "You must type the same password each time" msgstr "Bisogna inserire la stessa password ogni volta" #: forms.py:96 msgid "I have read and agree to the Terms of Service" msgstr "Dichiaro di aver letto e di approvare le Condizioni di Servizio" #: forms.py:105 msgid "You must agree to the terms to register" msgstr "Per registrarsi bisogna approvare le condizioni" #: forms.py:124 msgid "This email address is already in use. Please supply a different email " "address." msgstr "Questo indirizzo email è già in uso. Inserisci un altro indirizzo email." #: forms.py:149 msgid "Registration using free email addresses is prohibited. Please supply a " "different email address." msgstr "La registrazione con indirizzi email gratis non è permessa. " "Inserisci un altro indirizzo email." #: models.py:188 msgid "user" msgstr "utente" #: models.py:189 msgid "activation key" msgstr "chiave di attivazione" #: models.py:194 msgid "registration profile" msgstr "profilo di registrazione" #: models.py:195 msgid "registration profiles" msgstr "profili di registrazione" ================================================ FILE: dirigible/registration/locale/ja/LC_MESSAGES/django.po ================================================ # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # Shinya Okano , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: django-registration 0.4 \n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2007-09-19 19:30-0500\n" "PO-Revision-Date: 2008-01-31 10:20+0900\n" "Last-Translator: Shinya Okano \n" "Language-Team: Japanese \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: forms.py:38 msgid "username" msgstr "ユーザ名" #: forms.py:41 msgid "email address" msgstr "メールアドレス" #: forms.py:43 msgid "password" msgstr "パスワード" #: forms.py:45 msgid "password (again)" msgstr "パスワード (確認)" #: forms.py:54 msgid "Usernames can only contain letters, numbers and underscores" msgstr "ユーザ名には半角英数とアンダースコアのみが使用できます。" #: forms.py:59 msgid "This username is already taken. Please choose another." msgstr "このユーザ名は既に使用されています。他のユーザ名を指定してください。" #: forms.py:68 msgid "You must type the same password each time" msgstr "同じパスワードを入力する必要があります。" #: forms.py:96 msgid "I have read and agree to the Terms of Service" msgstr "サービス利用規約を読み、同意します。" #: forms.py:105 msgid "You must agree to the terms to register" msgstr "登録するためには規約に同意する必要があります。" #: forms.py:124 msgid "This email address is already in use. Please supply a different email address." msgstr "このメールアドレスは既に使用されています。他のメールアドレスを指定して下さい。" #: forms.py:149 msgid "Registration using free email addresses is prohibited. Please supply a different email address." msgstr "自由なメールアドレスを使用した登録は禁止されています。他のメールアドレスを指定してください。" #: models.py:188 msgid "user" msgstr "ユーザ" #: models.py:189 msgid "activation key" msgstr "アクティベーションキー" #: models.py:194 msgid "registration profile" msgstr "登録プロファイル" #: models.py:195 msgid "registration profiles" msgstr "登録プロファイル" ================================================ FILE: dirigible/registration/locale/nl/LC_MESSAGES/django.po ================================================ # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: registration\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2008-08-14 13:25+0200\n" "PO-Revision-Date: 2008-08-14 13:25+0200\n" "Last-Translator: Joost Cassee \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: forms.py:38 msgid "username" msgstr "gebruikersnaam" #: forms.py:41 msgid "email address" msgstr "e-mail adres" #: forms.py:43 msgid "password" msgstr "wachtwoord" #: forms.py:45 msgid "password (again)" msgstr "wachtwoord (opnieuw)" #: forms.py:54 msgid "Usernames can only contain letters, numbers and underscores" msgstr "Gebruikersnamen kunnen alleen letters, nummer en liggende streepjes bevatten." #: forms.py:59 msgid "This username is already taken. Please choose another." msgstr "Deze gebruikersnaam is reeds in gebruik. Kiest u alstublieft een andere gebruikersnaam." #: forms.py:71 msgid "You must type the same password each time" msgstr "U moet twee maal hetzelfde wachtwoord typen." #: forms.py:100 msgid "I have read and agree to the Terms of Service" msgstr "Ik heb de servicevoorwaarden gelezen en ga akkoord." #: forms.py:109 msgid "You must agree to the terms to register" msgstr "U moet akkoord gaan met de servicevoorwaarden om u te registreren." #: forms.py:125 msgid "This email address is already in use. Please supply a different email address." msgstr "Dit e-mail adres is reeds in gebruik. Kiest u alstublieft een ander e-mail adres." #: forms.py:151 msgid "Registration using free email addresses is prohibited. Please supply a different email address." msgstr "U kunt u niet registreren met een gratis e-mail adres. Kiest u alstublieft een ander e-mail adres." #: models.py:191 msgid "user" msgstr "gebruiker" #: models.py:192 msgid "activation key" msgstr "activatiecode" #: models.py:197 msgid "registration profile" msgstr "registratieprofiel" #: models.py:198 msgid "registration profiles" msgstr "registratieprofielen" ================================================ FILE: dirigible/registration/locale/pl/LC_MESSAGES/django.po ================================================ # Polish translation for django-registration. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the django-registration package. # Jarek Zgoda , 2007. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: 0.4\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2007-09-19 19:30-0500\n" "PO-Revision-Date: 2007-12-15 12:45+0100\n" "Last-Translator: Jarek Zgoda \n" "Language-Team: Polish \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: forms.py:38 msgid "username" msgstr "nazwa użytkownika" #: forms.py:41 msgid "email address" msgstr "adres email" #: forms.py:43 msgid "password" msgstr "hasło" #: forms.py:45 msgid "password (again)" msgstr "hasło (ponownie)" #: forms.py:54 msgid "Usernames can only contain letters, numbers and underscores" msgstr "" "Nazwa użytkownika może zawierać tylko litery, cyfry i znaki podkreślenia" #: forms.py:59 msgid "This username is already taken. Please choose another." msgstr "Ta nazwa użytkownika jest już zajęta. Wybierz inną." #: forms.py:68 msgid "You must type the same password each time" msgstr "Musisz wpisać to samo hasło w obu polach" #: forms.py:96 msgid "I have read and agree to the Terms of Service" msgstr "Przeczytałem regulamin i akceptuję go" #: forms.py:105 msgid "You must agree to the terms to register" msgstr "Musisz zaakceptować regulamin, aby się zarejestrować" #: forms.py:124 msgid "" "This email address is already in use. Please supply a different email " "address." msgstr "Ten adres email jest już używany. Użyj innego adresu email." #: forms.py:149 msgid "" "Registration using free email addresses is prohibited. Please supply a " "different email address." msgstr "" "Nie ma możliwości rejestracji przy użyciu darmowego adresu email. Użyj " "innego adresu email." #: models.py:188 msgid "user" msgstr "użytkownik" #: models.py:189 msgid "activation key" msgstr "klucz aktywacyjny" #: models.py:194 msgid "registration profile" msgstr "profil rejestracji" #: models.py:195 msgid "registration profiles" msgstr "profile rejestracji" ================================================ FILE: dirigible/registration/locale/pt_BR/LC_MESSAGES/django.po ================================================ # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2007-09-19 19:30-0500\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: forms.py:38 msgid "username" msgstr "usuário" #: forms.py:41 msgid "email address" msgstr "endereço de email" #: forms.py:43 msgid "password" msgstr "" #: forms.py:45 msgid "password (again)" msgstr "senha (novamente)" #: forms.py:54 msgid "Usernames can only contain letters, numbers and underscores" msgstr "Nomes de usuário apenas podem conter letras, números, e underscore" #: forms.py:59 msgid "This username is already taken. Please choose another." msgstr "Este nome de usuário já existe. Por favor, escolha outro." #: forms.py:68 msgid "You must type the same password each time" msgstr "Você deve escrever a mesma senha nos dois campos" #: forms.py:96 msgid "I have read and agree to the Terms of Service" msgstr "Eu lí e concordo com os Termos de Uso do serviço" #: forms.py:105 msgid "You must agree to the terms to register" msgstr "Você deve concordar com os termos para registrar-se" #: forms.py:124 msgid "" "This email address is already in use. Please supply a different email " "address." msgstr "Este endereço de email já está em uso. Por favor, informe um endereço de email diferente." #: forms.py:149 msgid "" "Registration using free email addresses is prohibited. Please supply a " "different email address." msgstr "Registrar-se com contas de email gratuitos está proibido. Por favor, informe um endereço de email diferente." #: models.py:188 msgid "user" msgstr "usuário" #: models.py:189 msgid "activation key" msgstr "chave de ativação" #: models.py:194 msgid "registration profile" msgstr "profile de registro" #: models.py:195 msgid "registration profiles" msgstr "profiles de registro" ================================================ FILE: dirigible/registration/locale/ru/LC_MESSAGES/django.po ================================================ # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2007-09-19 19:30-0500\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: forms.py:38 msgid "username" msgstr "имя пользователя" #: forms.py:41 msgid "email address" msgstr "адрес электронной почты" #: forms.py:43 msgid "password" msgstr "пароль" #: forms.py:45 msgid "password (again)" msgstr "пароль (верификация)" #: forms.py:54 msgid "Usernames can only contain letters, numbers and underscores" msgstr "Имя пользователя может содержать только буквы, цифры и подчеркивания" #: forms.py:59 msgid "This username is already taken. Please choose another." msgstr "Такое имя пользователя уже есть. Пожалуйста, выберите другое." #: forms.py:68 msgid "You must type the same password each time" msgstr "Вы должны вводить один и тот же пароль каждый раз" #: forms.py:96 msgid "I have read and agree to the Terms of Service" msgstr "Я прочитал и согласен с Правилами Использования" #: forms.py:105 msgid "You must agree to the terms to register" msgstr "Вы должны согласиться с Правилами для регистрации" #: forms.py:124 msgid "" "This email address is already in use. Please supply a different email " "address." msgstr "Этот адрес электронной почты уже используется. Пожалуйста, введите другой адрес." #: forms.py:149 msgid "" "Registration using free email addresses is prohibited. Please supply a " "different email address." msgstr "Регистрация с использованием свободных почтовых серверов запрещена. Пожалуйста, введите другой адрес электронной почты." #: models.py:188 msgid "user" msgstr "пользователь" #: models.py:189 msgid "activation key" msgstr "ключ активации" #: models.py:194 msgid "registration profile" msgstr "профиль регистрации" #: models.py:195 msgid "registration profiles" msgstr "профили регистрации" ================================================ FILE: dirigible/registration/locale/sr/LC_MESSAGES/django.po ================================================ # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: django-registration trunk\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2008-04-05 13:51+0200\n" "PO-Revision-Date: 2008-04-05 14:00+0100\n" "Last-Translator: Nebojsa Djordjevic \n" "Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" "X-Poedit-Language: Serbian\n" "X-Poedit-Country: YUGOSLAVIA\n" #: forms.py:38 msgid "username" msgstr "korisničko ime" #: forms.py:41 msgid "email address" msgstr "email adresa" #: forms.py:43 msgid "password" msgstr "šifra" #: forms.py:45 msgid "password (again)" msgstr "šifra (ponovo)" #: forms.py:54 msgid "Usernames can only contain letters, numbers and underscores" msgstr "Korisničko ime može da se sastoji samo od slova, brojeva i donje crte (\"_\")" #: forms.py:59 msgid "This username is already taken. Please choose another." msgstr "Korisničko ime je već zauzeto. Izaberite drugo." #: forms.py:71 msgid "You must type the same password each time" msgstr "Unete šifre se ne slažu" #: forms.py:100 msgid "I have read and agree to the Terms of Service" msgstr "Pročitao sam i slažem se sa uslovima korišćenja" #: forms.py:109 msgid "You must agree to the terms to register" msgstr "Morate se složiti sa uslovima korišćenja da bi ste se registrovali" #: forms.py:128 msgid "This email address is already in use. Please supply a different email address." msgstr "Ova e-mail adresa je već u upotrebi. Morate koristiti drugu e-mail adresu." #: forms.py:153 msgid "Registration using free email addresses is prohibited. Please supply a different email address." msgstr "Registracija korišćenjem besplatnig e-mail adresa je zabranjena. Morate uneti drugu e-mail adresu." #: models.py:188 msgid "user" msgstr "korisnik" #: models.py:189 msgid "activation key" msgstr "aktivacioni ključ" #: models.py:194 msgid "registration profile" msgstr "registracioni profil" #: models.py:195 msgid "registration profiles" msgstr "registracioni profili" ================================================ FILE: dirigible/registration/locale/sv/LC_MESSAGES/django.po ================================================ # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2008-03-23 18:59+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: Emil Stenström \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: .\forms.py:38 msgid "username" msgstr "Användarnamn" #: .\forms.py:41 msgid "email address" msgstr "E-postadress" #: .\forms.py:43 msgid "password" msgstr "Lösenord" #: .\forms.py:45 msgid "password (again)" msgstr "Lösenord (igen)" #: .\forms.py:54 msgid "Usernames can only contain letters, numbers and underscores" msgstr "Användarnamn får bara innehålla bokstäver, siffror och understreck" #: .\forms.py:59 msgid "This username is already taken. Please choose another." msgstr "Det användarnamnet är upptaget. Prova ett annat." #: .\forms.py:71 msgid "You must type the same password each time" msgstr "Båda lösenord måste vara lika" #: .\forms.py:100 msgid "I have read and agree to the Terms of Service" msgstr "Jag har läst och accepterar avtalet" #: .\forms.py:109 msgid "You must agree to the terms to register" msgstr "Du måste acceptera avtalet för att registrera dig" #: .\forms.py:128 msgid "" "This email address is already in use. Please supply a different email " "address." msgstr "Den e-postadressen är upptagen, använd an annan adress." #: .\forms.py:153 msgid "" "Registration using free email addresses is prohibited. Please supply a " "different email address." msgstr "Gratis e-postadresser är inte tillåtna, använd en annan adress." #: .\models.py:188 msgid "user" msgstr "Användare" #: .\models.py:189 msgid "activation key" msgstr "Aktiveringsnyckel" #: .\models.py:194 msgid "registration profile" msgstr "Profil" #: .\models.py:195 msgid "registration profiles" msgstr "Profiler" ================================================ FILE: dirigible/registration/locale/zh_CN/LC_MESSAGES/django.po ================================================ # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2007-09-19 19:30-0500\n" "PO-Revision-Date: 2008-03-20 23:22+0800\n" "Last-Translator: hutuworm \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: forms.py:38 msgid "username" msgstr "用户名" #: forms.py:41 msgid "email address" msgstr "Email 地址" #: forms.py:43 msgid "password" msgstr "密码" #: forms.py:45 msgid "password (again)" msgstr "密码(重复)" #: forms.py:54 msgid "Usernames can only contain letters, numbers and underscores" msgstr "用户名只能包含字母、数字和下划线" #: forms.py:59 msgid "This username is already taken. Please choose another." msgstr "该用户名已被占用,请另选一个。" #: forms.py:68 msgid "You must type the same password each time" msgstr "您必须输入两遍同样的密码" #: forms.py:96 msgid "I have read and agree to the Terms of Service" msgstr "我已阅读并同意该服务条款" #: forms.py:105 msgid "You must agree to the terms to register" msgstr "您必须同意注册条款" #: forms.py:124 msgid "This email address is already in use. Please supply a different email address." msgstr "该 Email 地址已有人使用,请提供一个另外的 Email 地址。" #: forms.py:149 msgid "Registration using free email addresses is prohibited. Please supply a different email address." msgstr "禁止使用免费 Email 地址注册,请提供一个另外的 Email 地址。" #: models.py:188 msgid "user" msgstr "用户" #: models.py:189 msgid "activation key" msgstr "激活密钥" #: models.py:194 msgid "registration profile" msgstr "注册信息" #: models.py:195 msgid "registration profiles" msgstr "注册信息" ================================================ FILE: dirigible/registration/locale/zh_TW/LC_MESSAGES/django.po ================================================ # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2007-09-19 19:30-0500\n" "PO-Revision-Date: 2008-03-20 23:22+0800\n" "Last-Translator: hutuworm \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: forms.py:38 msgid "username" msgstr "用戶名" #: forms.py:41 msgid "email address" msgstr "Email 地址" #: forms.py:43 msgid "password" msgstr "密碼" #: forms.py:45 msgid "password (again)" msgstr "密碼(重復)" #: forms.py:54 msgid "Usernames can only contain letters, numbers and underscores" msgstr "用戶名只能包含字母、數字和下劃線" #: forms.py:59 msgid "This username is already taken. Please choose another." msgstr "該用戶名已被佔用,請另選一個。" #: forms.py:68 msgid "You must type the same password each time" msgstr "您必須輸入兩遍同樣的密碼" #: forms.py:96 msgid "I have read and agree to the Terms of Service" msgstr "我已閱讀並同意該服務條款" #: forms.py:105 msgid "You must agree to the terms to register" msgstr "您必須同意注冊條款" #: forms.py:124 msgid "This email address is already in use. Please supply a different email address." msgstr "該 Email 地址已有人使用,請提供一個另外的 Email 地址。" #: forms.py:149 msgid "Registration using free email addresses is prohibited. Please supply a different email address." msgstr "禁止使用免費 Email 地址注冊,請提供一個另外的 Email 地址。" #: models.py:188 msgid "user" msgstr "用戶" #: models.py:189 msgid "activation key" msgstr "激活密鑰" #: models.py:194 msgid "registration profile" msgstr "注冊信息" #: models.py:195 msgid "registration profiles" msgstr "注冊信息" ================================================ FILE: dirigible/registration/management/__init__.py ================================================ ================================================ FILE: dirigible/registration/management/commands/__init__.py ================================================ ================================================ FILE: dirigible/registration/management/commands/cleanupregistration.py ================================================ """ A management command which deletes expired accounts (e.g., accounts which signed up but never activated) from the database. Calls ``RegistrationProfile.objects.delete_expired_users()``, which contains the actual logic for determining which accounts are deleted. """ from django.core.management.base import NoArgsCommand from registration.models import RegistrationProfile class Command(NoArgsCommand): help = "Delete expired user registrations from the database" def handle_noargs(self, **options): RegistrationProfile.objects.delete_expired_users() ================================================ FILE: dirigible/registration/migrations/0001_initial.py ================================================ # -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import models, migrations from django.conf import settings class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ migrations.CreateModel( name='RegistrationProfile', fields=[ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('activation_key', models.CharField(max_length=40, verbose_name='activation key')), ('user', models.ForeignKey(verbose_name='user', to=settings.AUTH_USER_MODEL, unique=True)), ], options={ 'verbose_name': 'registration profile', 'verbose_name_plural': 'registration profiles', }, bases=(models.Model,), ), ] ================================================ FILE: dirigible/registration/migrations/__init__.py ================================================ ================================================ FILE: dirigible/registration/models.py ================================================ import datetime import random import re import sha from django.conf import settings from django.db import models from django.template.loader import render_to_string from django.utils.translation import ugettext_lazy as _ from django.contrib.auth.models import User from django.contrib.sites.models import Site SHA1_RE = re.compile('^[a-f0-9]{40}$') class RegistrationManager(models.Manager): """ Custom manager for the ``RegistrationProfile`` model. The methods defined here provide shortcuts for account creation and activation (including generation and emailing of activation keys), and for cleaning out expired inactive accounts. """ def activate_user(self, activation_key): """ Validate an activation key and activate the corresponding ``User`` if valid. If the key is valid and has not expired, return the ``User`` after activating. If the key is not valid or has expired, return ``False``. If the key is valid but the ``User`` is already active, return ``False``. To prevent reactivation of an account which has been deactivated by site administrators, the activation key is reset to the string ``ALREADY_ACTIVATED`` after successful activation. """ # Make sure the key we're trying conforms to the pattern of a # SHA1 hash; if it doesn't, no point trying to look it up in # the database. if SHA1_RE.search(activation_key): try: profile = self.get(activation_key=activation_key) except self.model.DoesNotExist: return False if not profile.activation_key_expired(): user = profile.user user.is_active = True user.save() profile.activation_key = self.model.ACTIVATED profile.save() return user return False def create_inactive_user(self, username, password, email, send_email=True, profile_callback=None): """ Create a new, inactive ``User``, generates a ``RegistrationProfile`` and email its activation key to the ``User``, returning the new ``User``. To disable the email, call with ``send_email=False``. The activation email will make use of two templates: ``registration/activation_email_subject.txt`` This template will be used for the subject line of the email. It receives one context variable, ``site``, which is the currently-active ``django.contrib.sites.models.Site`` instance. Because it is used as the subject line of an email, this template's output **must** be only a single line of text; output longer than one line will be forcibly joined into only a single line. ``registration/activation_email.txt`` This template will be used for the body of the email. It will receive three context variables: ``activation_key`` will be the user's activation key (for use in constructing a URL to activate the account), ``expiration_days`` will be the number of days for which the key will be valid and ``site`` will be the currently-active ``django.contrib.sites.models.Site`` instance. To enable creation of a custom user profile along with the ``User`` (e.g., the model specified in the ``AUTH_PROFILE_MODULE`` setting), define a function which knows how to create and save an instance of that model with appropriate default values, and pass it as the keyword argument ``profile_callback``. This function should accept one keyword argument: ``user`` The ``User`` to relate the profile to. """ new_user = User.objects.create_user(username, email, password) new_user.is_active = False new_user.save() registration_profile = self.create_profile(new_user) if profile_callback is not None: profile_callback(user=new_user) if send_email: from django.core.mail import send_mail current_site = Site.objects.get_current() subject = render_to_string('registration/activation_email_subject.txt', { 'site': current_site }) # Email subject *must not* contain newlines subject = ''.join(subject.splitlines()) message = render_to_string('registration/activation_email.txt', { 'activation_key': registration_profile.activation_key, 'expiration_days': settings.ACCOUNT_ACTIVATION_DAYS, 'site': current_site }) send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [new_user.email]) return new_user def create_profile(self, user): """ Create a ``RegistrationProfile`` for a given ``User``, and return the ``RegistrationProfile``. The activation key for the ``RegistrationProfile`` will be a SHA1 hash, generated from a combination of the ``User``'s username and a random salt. """ salt = sha.new(str(random.random())).hexdigest()[:5] activation_key = sha.new(salt+user.username).hexdigest() return self.create(user=user, activation_key=activation_key) def delete_expired_users(self): """ Remove expired instances of ``RegistrationProfile`` and their associated ``User``s. Accounts to be deleted are identified by searching for instances of ``RegistrationProfile`` with expired activation keys, and then checking to see if their associated ``User`` instances have the field ``is_active`` set to ``False``; any ``User`` who is both inactive and has an expired activation key will be deleted. It is recommended that this method be executed regularly as part of your routine site maintenance; this application provides a custom management command which will call this method, accessible as ``manage.py cleanupregistration``. Regularly clearing out accounts which have never been activated serves two useful purposes: 1. It alleviates the ocasional need to reset a ``RegistrationProfile`` and/or re-send an activation email when a user does not receive or does not act upon the initial activation email; since the account will be deleted, the user will be able to simply re-register and receive a new activation key. 2. It prevents the possibility of a malicious user registering one or more accounts and never activating them (thus denying the use of those usernames to anyone else); since those accounts will be deleted, the usernames will become available for use again. If you have a troublesome ``User`` and wish to disable their account while keeping it in the database, simply delete the associated ``RegistrationProfile``; an inactive ``User`` which does not have an associated ``RegistrationProfile`` will not be deleted. """ for profile in self.all(): if profile.activation_key_expired(): user = profile.user if not user.is_active: user.delete() class RegistrationProfile(models.Model): """ A simple profile which stores an activation key for use during user account registration. Generally, you will not want to interact directly with instances of this model; the provided manager includes methods for creating and activating new accounts, as well as for cleaning out accounts which have never been activated. While it is possible to use this model as the value of the ``AUTH_PROFILE_MODULE`` setting, it's not recommended that you do so. This model's sole purpose is to store data temporarily during account registration and activation, and a mechanism for automatically creating an instance of a site-specific profile model is provided via the ``create_inactive_user`` on ``RegistrationManager``. """ ACTIVATED = u"ALREADY_ACTIVATED" user = models.ForeignKey(User, unique=True, verbose_name=_('user')) activation_key = models.CharField(_('activation key'), max_length=40) objects = RegistrationManager() class Meta: verbose_name = _('registration profile') verbose_name_plural = _('registration profiles') def __unicode__(self): return u"Registration information for %s" % self.user def activation_key_expired(self): """ Determine whether this ``RegistrationProfile``'s activation key has expired, returning a boolean -- ``True`` if the key has expired. Key expiration is determined by a two-step process: 1. If the user has already activated, the key will have been reset to the string ``ALREADY_ACTIVATED``. Re-activating is not permitted, and so this method returns ``True`` in this case. 2. Otherwise, the date the user signed up is incremented by the number of days specified in the setting ``ACCOUNT_ACTIVATION_DAYS`` (which should be the number of days after signup during which a user is allowed to activate their account); if the result is less than or equal to the current date, the key has expired and this method returns ``True``. """ expiration_date = datetime.timedelta(days=settings.ACCOUNT_ACTIVATION_DAYS) return self.activation_key == self.ACTIVATED or \ (self.user.date_joined + expiration_date <= datetime.datetime.now()) activation_key_expired.boolean = True ================================================ FILE: dirigible/registration/tests.py ================================================ """ Unit tests for django-registration. These tests assume that you've completed all the prerequisites for getting django-registration running in the default setup, to wit: 1. You have ``registration`` in your ``INSTALLED_APPS`` setting. 2. You have created all of the templates mentioned in this application's documentation. 3. You have added the setting ``ACCOUNT_ACTIVATION_DAYS`` to your settings file. 4. You have URL patterns pointing to the registration and activation views, with the names ``registration_register`` and ``registration_activate``, respectively, and a URL pattern named 'registration_complete'. """ import datetime import sha from django.conf import settings from django.contrib.auth.models import User from django.core import mail from django.core import management from django.core.urlresolvers import reverse from django.test import TestCase from django.conf import settings from registration import forms from registration.models import RegistrationProfile class RegistrationTestCase(TestCase): """ Base class for the test cases; this sets up two users -- one expired, one not -- which are used to exercise various parts of the application. """ def setUp(self): self.sample_user = RegistrationProfile.objects.create_inactive_user(username='alice', password='secret', email='alice@example.com') self.expired_user = RegistrationProfile.objects.create_inactive_user(username='bob', password='swordfish', email='bob@example.com') self.expired_user.date_joined -= datetime.timedelta(days=settings.ACCOUNT_ACTIVATION_DAYS + 1) self.expired_user.save() class RegistrationModelTests(RegistrationTestCase): """ Tests for the model-oriented functionality of django-registration, including ``RegistrationProfile`` and its custom manager. """ def test_new_user_is_inactive(self): """ Test that a newly-created user is inactive. """ self.failIf(self.sample_user.is_active) def test_registration_profile_created(self): """ Test that a ``RegistrationProfile`` is created for a new user. """ self.assertEqual(RegistrationProfile.objects.count(), 2) def test_activation_email(self): """ Test that user signup sends an activation email. """ self.assertEqual(len(mail.outbox), 2) def test_activation(self): """ Test that user activation actually activates the user and properly resets the activation key, and fails for an already-active or expired user, or an invalid key. """ # Activating a valid user returns the user. self.failUnlessEqual(RegistrationProfile.objects.activate_user(RegistrationProfile.objects.get(user=self.sample_user).activation_key).pk, self.sample_user.pk) # The activated user must now be active. self.failUnless(User.objects.get(pk=self.sample_user.pk).is_active) # The activation key must now be reset to the "already activated" constant. self.failUnlessEqual(RegistrationProfile.objects.get(user=self.sample_user).activation_key, RegistrationProfile.ACTIVATED) # Activating an expired user returns False. self.failIf(RegistrationProfile.objects.activate_user(RegistrationProfile.objects.get(user=self.expired_user).activation_key)) # Activating from a key that isn't a SHA1 hash returns False. self.failIf(RegistrationProfile.objects.activate_user('foo')) # Activating from a key that doesn't exist returns False. self.failIf(RegistrationProfile.objects.activate_user(sha.new('foo').hexdigest())) def test_account_expiration_condition(self): """ Test that ``RegistrationProfile.activation_key_expired()`` returns ``True`` for expired users and for active users, and ``False`` otherwise. """ # Unexpired user returns False. self.failIf(RegistrationProfile.objects.get(user=self.sample_user).activation_key_expired()) # Expired user returns True. self.failUnless(RegistrationProfile.objects.get(user=self.expired_user).activation_key_expired()) # Activated user returns True. RegistrationProfile.objects.activate_user(RegistrationProfile.objects.get(user=self.sample_user).activation_key) self.failUnless(RegistrationProfile.objects.get(user=self.sample_user).activation_key_expired()) def test_expired_user_deletion(self): """ Test that ``RegistrationProfile.objects.delete_expired_users()`` deletes only inactive users whose activation window has expired. """ RegistrationProfile.objects.delete_expired_users() self.assertEqual(RegistrationProfile.objects.count(), 1) def test_management_command(self): """ Test that ``manage.py cleanupregistration`` functions correctly. """ management.call_command('cleanupregistration') self.assertEqual(RegistrationProfile.objects.count(), 1) class RegistrationFormTests(RegistrationTestCase): """ Tests for the forms and custom validation logic included in django-registration. """ def test_registration_form(self): """ Test that ``RegistrationForm`` enforces username constraints and matching passwords. """ invalid_data_dicts = [ # Non-alphanumeric username. { 'data': { 'username': 'foo/bar', 'email': 'foo@example.com', 'password1': 'foo', 'password2': 'foo' }, 'error': ('username', [u"Enter a valid value."]) }, # Already-existing username. { 'data': { 'username': 'alice', 'email': 'alice@example.com', 'password1': 'secret', 'password2': 'secret' }, 'error': ('username', [u"This username is already taken. Please choose another."]) }, # Mismatched passwords. { 'data': { 'username': 'foo', 'email': 'foo@example.com', 'password1': 'foo', 'password2': 'bar' }, 'error': ('__all__', [u"You must type the same password each time"]) }, ] for invalid_dict in invalid_data_dicts: form = forms.RegistrationForm(data=invalid_dict['data']) self.failIf(form.is_valid()) self.assertEqual(form.errors[invalid_dict['error'][0]], invalid_dict['error'][1]) form = forms.RegistrationForm(data={ 'username': 'foo', 'email': 'foo@example.com', 'password1': 'foo', 'password2': 'foo' }) self.failUnless(form.is_valid()) def test_registration_form_tos(self): """ Test that ``RegistrationFormTermsOfService`` requires agreement to the terms of service. """ form = forms.RegistrationFormTermsOfService(data={ 'username': 'foo', 'email': 'foo@example.com', 'password1': 'foo', 'password2': 'foo' }) self.failIf(form.is_valid()) self.assertEqual(form.errors['tos'], [u"You must agree to the terms to register"]) form = forms.RegistrationFormTermsOfService(data={ 'username': 'foo', 'email': 'foo@example.com', 'password1': 'foo', 'password2': 'foo', 'tos': 'on' }) self.failUnless(form.is_valid()) def test_registration_form_unique_email(self): """ Test that ``RegistrationFormUniqueEmail`` validates uniqueness of email addresses. """ form = forms.RegistrationFormUniqueEmail(data={ 'username': 'foo', 'email': 'alice@example.com', 'password1': 'foo', 'password2': 'foo' }) self.failIf(form.is_valid()) self.assertEqual(form.errors['email'], [u"This email address is already in use. Please supply a different email address."]) form = forms.RegistrationFormUniqueEmail(data={ 'username': 'foo', 'email': 'foo@example.com', 'password1': 'foo', 'password2': 'foo' }) self.failUnless(form.is_valid()) def test_registration_form_no_free_email(self): """ Test that ``RegistrationFormNoFreeEmail`` disallows registration with free email addresses. """ base_data = { 'username': 'foo', 'password1': 'foo', 'password2': 'foo' } for domain in ('aim.com', 'aol.com', 'email.com', 'gmail.com', 'googlemail.com', 'hotmail.com', 'hushmail.com', 'msn.com', 'mail.ru', 'mailinator.com', 'live.com'): invalid_data = base_data.copy() invalid_data['email'] = u"foo@%s" % domain form = forms.RegistrationFormNoFreeEmail(data=invalid_data) self.failIf(form.is_valid()) self.assertEqual(form.errors['email'], [u"Registration using free email addresses is prohibited. Please supply a different email address."]) base_data['email'] = 'foo@example.com' form = forms.RegistrationFormNoFreeEmail(data=base_data) self.failUnless(form.is_valid()) class RegistrationViewTests(RegistrationTestCase): """ Tests for the views included in django-registration. """ def test_registration_view(self): """ Test that the registration view rejects invalid submissions, and creates a new user and redirects after a valid submission. """ # Invalid data fails. response = self.client.post(reverse('registration_register'), data={ 'username': 'alice', # Will fail on username uniqueness. 'email': 'foo@example.com', 'password1': 'foo', 'password2': 'foo' }) self.assertEqual(response.status_code, 200) self.failUnless(response.context['form']) self.failUnless(response.context['form'].errors) response = self.client.post(reverse('registration_register'), data={ 'username': 'foo', 'email': 'foo@example.com', 'password1': 'foo', 'password2': 'foo' }) self.assertEqual(response.status_code, 302) self.assertEqual(response['Location'], 'http://testserver%s' % reverse('registration_complete')) self.assertEqual(RegistrationProfile.objects.count(), 3) def test_activation_view(self): """ Test that the activation view activates the user from a valid key and fails if the key is invalid or has expired. """ # Valid user puts the user account into the context. response = self.client.get(reverse('registration_activate', kwargs={ 'activation_key': RegistrationProfile.objects.get(user=self.sample_user).activation_key })) self.assertEqual(response.status_code, 200) self.assertEqual(response.context['account'].pk, self.sample_user.pk) # Expired user sets the account to False. response = self.client.get(reverse('registration_activate', kwargs={ 'activation_key': RegistrationProfile.objects.get(user=self.expired_user).activation_key })) self.failIf(response.context['account']) # Invalid key gets to the view, but sets account to False. response = self.client.get(reverse('registration_activate', kwargs={ 'activation_key': 'foo' })) self.failIf(response.context['account']) # Nonexistent key sets the account to False. response = self.client.get(reverse('registration_activate', kwargs={ 'activation_key': sha.new('foo').hexdigest() })) self.failIf(response.context['account']) ================================================ FILE: dirigible/registration/urls.py ================================================ """ URLConf for Django user registration and authentication. If the default behavior of the registration views is acceptable to you, simply use a line like this in your root URLConf to set up the default URLs for registration:: (r'^accounts/', include('registration.urls')), This will also automatically set up the views in ``django.contrib.auth`` at sensible default locations. But if you'd like to customize the behavior (e.g., by passing extra arguments to the various views) or split up the URLs, feel free to set up your own URL patterns for these views instead. If you do, it's a good idea to use the names ``registration_activate``, ``registration_complete`` and ``registration_register`` for the various steps of the user-signup process. """ from django.conf.urls import patterns, url from django.contrib.auth import views as auth_views from django.views.generic import TemplateView from registration.views import activate from registration.views import register urlpatterns = patterns( '', # Activation keys get matched by \w+ instead of the more specific # [a-fA-F0-9]{40} because a bad activation key should still get to the view; # that way it can return a sensible "invalid key" message instead of a # confusing 404. url(r'^activate/(?P\w+)/$', activate, name='registration_activate'), url(r'^login/$', auth_views.login, {'template_name': 'registration/login.html'}, name='auth_login'), url(r'^logout/$', auth_views.logout, {'template_name': 'registration/logout.html'}, name='auth_logout'), url(r'^password/change/$', auth_views.password_change, name='auth_password_change'), url(r'^password/change/done/$', auth_views.password_change_done, name='auth_password_change_done'), url(r'^password/reset/$', auth_views.password_reset, name='auth_password_reset'), url(r'^password/reset/confirm/(?P[0-9A-Za-z]+)-(?P.+)/$', auth_views.password_reset_confirm, name='auth_password_reset_confirm'), url(r'^password/reset/complete/$', auth_views.password_reset_complete, name='auth_password_reset_complete'), url(r'^password/reset/done/$', auth_views.password_reset_done, name='auth_password_reset_done'), url(r'^register/$', register, name='registration_register'), url( r'^register/complete/$', TemplateView.as_view(template='registration/registration_complete.html'), name='registration_complete' ), ) ================================================ FILE: dirigible/registration/views.py ================================================ """ Views which allow users to create and activate accounts. """ from django.conf import settings from django.core.urlresolvers import reverse from django.http import HttpResponseRedirect from django.shortcuts import render_to_response from django.template import RequestContext from registration.forms import RegistrationForm from registration.models import RegistrationProfile def activate(request, activation_key, template_name='registration/activate.html', extra_context=None): """ Activate a ``User``'s account from an activation key, if their key is valid and hasn't expired. By default, use the template ``registration/activate.html``; to change this, pass the name of a template as the keyword argument ``template_name``. **Required arguments** ``activation_key`` The activation key to validate and use for activating the ``User``. **Optional arguments** ``extra_context`` A dictionary of variables to add to the template context. Any callable object in this dictionary will be called to produce the end result which appears in the context. ``template_name`` A custom template to use. **Context:** ``account`` The ``User`` object corresponding to the account, if the activation was successful. ``False`` if the activation was not successful. ``expiration_days`` The number of days for which activation keys stay valid after registration. Any extra variables supplied in the ``extra_context`` argument (see above). **Template:** registration/activate.html or ``template_name`` keyword argument. """ activation_key = activation_key.lower() # Normalize before trying anything with it. account = RegistrationProfile.objects.activate_user(activation_key) if extra_context is None: extra_context = {} context = RequestContext(request) for key, value in extra_context.items(): context[key] = callable(value) and value() or value return render_to_response(template_name, { 'account': account, 'expiration_days': settings.ACCOUNT_ACTIVATION_DAYS }, context_instance=context) def register(request, success_url=None, form_class=RegistrationForm, profile_callback=None, template_name='registration/registration_form.html', extra_context=None): """ Allow a new user to register an account. Following successful registration, issue a redirect; by default, this will be whatever URL corresponds to the named URL pattern ``registration_complete``, which will be ``/accounts/register/complete/`` if using the included URLConf. To change this, point that named pattern at another URL, or pass your preferred URL as the keyword argument ``success_url``. By default, ``registration.forms.RegistrationForm`` will be used as the registration form; to change this, pass a different form class as the ``form_class`` keyword argument. The form class you specify must have a method ``save`` which will create and return the new ``User``, and that method must accept the keyword argument ``profile_callback`` (see below). To enable creation of a site-specific user profile object for the new user, pass a function which will create the profile object as the keyword argument ``profile_callback``. See ``RegistrationManager.create_inactive_user`` in the file ``models.py`` for details on how to write this function. By default, use the template ``registration/registration_form.html``; to change this, pass the name of a template as the keyword argument ``template_name``. **Required arguments** None. **Optional arguments** ``form_class`` The form class to use for registration. ``extra_context`` A dictionary of variables to add to the template context. Any callable object in this dictionary will be called to produce the end result which appears in the context. ``profile_callback`` A function which will be used to create a site-specific profile instance for the new ``User``. ``success_url`` The URL to redirect to on successful registration. ``template_name`` A custom template to use. **Context:** ``form`` The registration form. Any extra variables supplied in the ``extra_context`` argument (see above). **Template:** registration/registration_form.html or ``template_name`` keyword argument. """ if request.method == 'POST': form = form_class(data=request.POST, files=request.FILES) if form.is_valid(): new_user = form.save(profile_callback=profile_callback) # success_url needs to be dynamically generated here; setting a # a default value using reverse() will cause circular-import # problems with the default URLConf for this application, which # imports this file. return HttpResponseRedirect(success_url or reverse('registration_complete')) else: form = form_class() if extra_context is None: extra_context = {} context = RequestContext(request) for key, value in extra_context.items(): context[key] = callable(value) and value() or value return render_to_response(template_name, { 'form': form }, context_instance=context) ================================================ FILE: dirigible/shared/__init__.py ================================================ ================================================ FILE: dirigible/shared/models.py ================================================ from django.db import models # Create your models here. ================================================ FILE: dirigible/shared/static/ace/ace-uncompressed.js ================================================ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Ajax.org Code Editor (ACE). * * The Initial Developer of the Original Code is * Ajax.org B.V. * Portions created by the Initial Developer are Copyright (C) 2010 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Fabian Jakobs * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ /** * Define a module along with a payload * @param module a name for the payload * @param payload a function to call with (require, exports, module) params */ (function() { if (window.require) { require.packaged = true; return; } var _define = function(module, deps, payload) { if (typeof module !== 'string') { if (_define.original) _define.original.apply(window, arguments); else { console.error('dropping module because define wasn\'t a string.'); console.trace(); } return; } if (arguments.length == 2) payload = deps; if (!define.modules) define.modules = {}; define.modules[module] = payload; }; if (window.define) _define.original = window.define; window.define = _define; /** * Get at functionality define()ed using the function above */ var _require = function(module, callback) { if (Object.prototype.toString.call(module) === "[object Array]") { var params = []; for (var i = 0, l = module.length; i < l; ++i) { var dep = lookup(module[i]); if (!dep && _require.original) return _require.original.apply(window, arguments); params.push(dep); } if (callback) { callback.apply(null, params); } } else if (typeof module === 'string') { var payload = lookup(module); if (!payload && _require.original) return _require.original.apply(window, arguments); if (callback) { callback(); } return payload; } else { if (_require.original) return _require.original.apply(window, arguments); } }; if (window.require) _require.original = window.require; window.require = _require; require.packaged = true; /** * Internal function to lookup moduleNames and resolve them by calling the * definition function if needed. */ var lookup = function(moduleName) { var module = define.modules[moduleName]; if (module == null) { console.error('Missing module: ' + moduleName); return null; } if (typeof module === 'function') { var exports = {}; module(require, exports, { id: moduleName, uri: '' }); // cache the resulting module object for next time define.modules[moduleName] = exports; return exports; } return module; }; })();// -- kriskowal Kris Kowal Copyright (C) 2009-2010 MIT License // -- tlrobinson Tom Robinson Copyright (C) 2009-2010 MIT License (Narwhal Project) // -- dantman Daniel Friesen Copyright(C) 2010 XXX No License Specified // -- fschaefer Florian Schäfer Copyright (C) 2010 MIT License /*! Copyright (c) 2009, 280 North Inc. http://280north.com/ MIT License. http://github.com/280north/narwhal/blob/master/README.md */ define('pilot/fixoldbrowsers', function(require, exports, module) { /** * Brings an environment as close to ECMAScript 5 compliance * as is possible with the facilities of erstwhile engines. * * ES5 Draft * http://www.ecma-international.org/publications/files/drafts/tc39-2009-050.pdf * * NOTE: this is a draft, and as such, the URL is subject to change. If the * link is broken, check in the parent directory for the latest TC39 PDF. * http://www.ecma-international.org/publications/files/drafts/ * * Previous ES5 Draft * http://www.ecma-international.org/publications/files/drafts/tc39-2009-025.pdf * This is a broken link to the previous draft of ES5 on which most of the * numbered specification references and quotes herein were taken. Updating * these references and quotes to reflect the new document would be a welcome * volunteer project. * * @module */ /*whatsupdoc*/ // this is often accessed, so avoid multiple dereference costs universally var has = Object.prototype.hasOwnProperty; // // Array // ===== // // ES5 15.4.3.2 if (!Array.isArray) { Array.isArray = function(obj) { return Object.prototype.toString.call(obj) == "[object Array]"; }; } // ES5 15.4.4.18 if (!Array.prototype.forEach) { Array.prototype.forEach = function(block, thisObject) { var len = +this.length; for (var i = 0; i < len; i++) { if (i in this) { block.call(thisObject, this[i], i, this); } } }; } // ES5 15.4.4.19 // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/map if (!Array.prototype.map) { Array.prototype.map = function(fun /*, thisp*/) { var len = +this.length; if (typeof fun != "function") throw new TypeError(); var res = new Array(len); var thisp = arguments[1]; for (var i = 0; i < len; i++) { if (i in this) res[i] = fun.call(thisp, this[i], i, this); } return res; }; } // ES5 15.4.4.20 if (!Array.prototype.filter) { Array.prototype.filter = function (block /*, thisp */) { var values = []; var thisp = arguments[1]; for (var i = 0; i < this.length; i++) if (block.call(thisp, this[i])) values.push(this[i]); return values; }; } // ES5 15.4.4.16 if (!Array.prototype.every) { Array.prototype.every = function (block /*, thisp */) { var thisp = arguments[1]; for (var i = 0; i < this.length; i++) if (!block.call(thisp, this[i])) return false; return true; }; } // ES5 15.4.4.17 if (!Array.prototype.some) { Array.prototype.some = function (block /*, thisp */) { var thisp = arguments[1]; for (var i = 0; i < this.length; i++) if (block.call(thisp, this[i])) return true; return false; }; } // ES5 15.4.4.21 // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/reduce if (!Array.prototype.reduce) { Array.prototype.reduce = function(fun /*, initial*/) { var len = +this.length; if (typeof fun != "function") throw new TypeError(); // no value to return if no initial value and an empty array if (len == 0 && arguments.length == 1) throw new TypeError(); var i = 0; if (arguments.length >= 2) { var rv = arguments[1]; } else { do { if (i in this) { rv = this[i++]; break; } // if array contains no values, no initial value to return if (++i >= len) throw new TypeError(); } while (true); } for (; i < len; i++) { if (i in this) rv = fun.call(null, rv, this[i], i, this); } return rv; }; } // ES5 15.4.4.22 // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/reduceRight if (!Array.prototype.reduceRight) { Array.prototype.reduceRight = function(fun /*, initial*/) { var len = +this.length; if (typeof fun != "function") throw new TypeError(); // no value to return if no initial value, empty array if (len == 0 && arguments.length == 1) throw new TypeError(); var i = len - 1; if (arguments.length >= 2) { var rv = arguments[1]; } else { do { if (i in this) { rv = this[i--]; break; } // if array contains no values, no initial value to return if (--i < 0) throw new TypeError(); } while (true); } for (; i >= 0; i--) { if (i in this) rv = fun.call(null, rv, this[i], i, this); } return rv; }; } // ES5 15.4.4.14 if (!Array.prototype.indexOf) { Array.prototype.indexOf = function (value /*, fromIndex */ ) { var length = this.length; if (!length) return -1; var i = arguments[1] || 0; if (i >= length) return -1; if (i < 0) i += length; for (; i < length; i++) { if (!has.call(this, i)) continue; if (value === this[i]) return i; } return -1; }; } // ES5 15.4.4.15 if (!Array.prototype.lastIndexOf) { Array.prototype.lastIndexOf = function (value /*, fromIndex */) { var length = this.length; if (!length) return -1; var i = arguments[1] || length; if (i < 0) i += length; i = Math.min(i, length - 1); for (; i >= 0; i--) { if (!has.call(this, i)) continue; if (value === this[i]) return i; } return -1; }; } // // Object // ====== // // ES5 15.2.3.2 if (!Object.getPrototypeOf) { // https://github.com/kriskowal/es5-shim/issues#issue/2 // http://ejohn.org/blog/objectgetprototypeof/ // recommended by fschaefer on github Object.getPrototypeOf = function (object) { return object.__proto__ || object.constructor.prototype; // or undefined if not available in this engine }; } // ES5 15.2.3.3 if (!Object.getOwnPropertyDescriptor) { Object.getOwnPropertyDescriptor = function (object, property) { if (typeof object !== "object" && typeof object !== "function" || object === null) throw new TypeError("Object.getOwnPropertyDescriptor called on a non-object"); return has.call(object, property) ? { value: object[property], enumerable: true, configurable: true, writeable: true } : undefined; }; } // ES5 15.2.3.4 if (!Object.getOwnPropertyNames) { Object.getOwnPropertyNames = function (object) { return Object.keys(object); }; } // ES5 15.2.3.5 if (!Object.create) { Object.create = function(prototype, properties) { var object; if (prototype === null) { object = {"__proto__": null}; } else { if (typeof prototype != "object") throw new TypeError("typeof prototype["+(typeof prototype)+"] != 'object'"); var Type = function () {}; Type.prototype = prototype; object = new Type(); } if (typeof properties !== "undefined") Object.defineProperties(object, properties); return object; }; } // ES5 15.2.3.6 if (!Object.defineProperty) { Object.defineProperty = function(object, property, descriptor) { if (typeof descriptor == "object" && object.__defineGetter__) { if (has.call(descriptor, "value")) { if (!object.__lookupGetter__(property) && !object.__lookupSetter__(property)) // data property defined and no pre-existing accessors object[property] = descriptor.value; if (has.call(descriptor, "get") || has.call(descriptor, "set")) // descriptor has a value property but accessor already exists throw new TypeError("Object doesn't support this action"); } // fail silently if "writable", "enumerable", or "configurable" // are requested but not supported /* // alternate approach: if ( // can't implement these features; allow false but not true !(has.call(descriptor, "writable") ? descriptor.writable : true) || !(has.call(descriptor, "enumerable") ? descriptor.enumerable : true) || !(has.call(descriptor, "configurable") ? descriptor.configurable : true) ) throw new RangeError( "This implementation of Object.defineProperty does not " + "support configurable, enumerable, or writable." ); */ else if (typeof descriptor.get == "function") object.__defineGetter__(property, descriptor.get); if (typeof descriptor.set == "function") object.__defineSetter__(property, descriptor.set); } return object; }; } // ES5 15.2.3.7 if (!Object.defineProperties) { Object.defineProperties = function(object, properties) { for (var property in properties) { if (has.call(properties, property)) Object.defineProperty(object, property, properties[property]); } return object; }; } // ES5 15.2.3.8 if (!Object.seal) { Object.seal = function (object) { // this is misleading and breaks feature-detection, but // allows "securable" code to "gracefully" degrade to working // but insecure code. return object; }; } // ES5 15.2.3.9 if (!Object.freeze) { Object.freeze = function (object) { // this is misleading and breaks feature-detection, but // allows "securable" code to "gracefully" degrade to working // but insecure code. return object; }; } // detect a Rhino bug and patch it try { Object.freeze(function () {}); } catch (exception) { Object.freeze = (function (freeze) { return function (object) { if (typeof object == "function") { return object; } else { return freeze(object); } }; })(Object.freeze); } // ES5 15.2.3.10 if (!Object.preventExtensions) { Object.preventExtensions = function (object) { // this is misleading and breaks feature-detection, but // allows "securable" code to "gracefully" degrade to working // but insecure code. return object; }; } // ES5 15.2.3.11 if (!Object.isSealed) { Object.isSealed = function (object) { return false; }; } // ES5 15.2.3.12 if (!Object.isFrozen) { Object.isFrozen = function (object) { return false; }; } // ES5 15.2.3.13 if (!Object.isExtensible) { Object.isExtensible = function (object) { return true; }; } // ES5 15.2.3.14 // http://whattheheadsaid.com/2010/10/a-safer-object-keys-compatibility-implementation if (!Object.keys) { var hasDontEnumBug = true, dontEnums = [ 'toString', 'toLocaleString', 'valueOf', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable', 'constructor' ], dontEnumsLength = dontEnums.length; for (var key in {"toString": null}) hasDontEnumBug = false; Object.keys = function (object) { if ( typeof object !== "object" && typeof object !== "function" || object === null ) throw new TypeError("Object.keys called on a non-object"); var keys = []; for (var name in object) { if (has.call(object, name)) { keys.push(name); } } if (hasDontEnumBug) { for (var i = 0, ii = dontEnumsLength; i < ii; i++) { var dontEnum = dontEnums[i]; if (has.call(object, dontEnum)) { keys.push(dontEnum); } } } return keys; }; } // // Date // ==== // // ES5 15.9.5.43 // Format a Date object as a string according to a subset of the ISO-8601 standard. // Useful in Atom, among other things. if (!Date.prototype.toISOString) { Date.prototype.toISOString = function() { return ( this.getUTCFullYear() + "-" + (this.getUTCMonth() + 1) + "-" + this.getUTCDate() + "T" + this.getUTCHours() + ":" + this.getUTCMinutes() + ":" + this.getUTCSeconds() + "Z" ); } } // ES5 15.9.4.4 if (!Date.now) { Date.now = function () { return new Date().getTime(); }; } // ES5 15.9.5.44 if (!Date.prototype.toJSON) { Date.prototype.toJSON = function (key) { // This function provides a String representation of a Date object for // use by JSON.stringify (15.12.3). When the toJSON method is called // with argument key, the following steps are taken: // 1. Let O be the result of calling ToObject, giving it the this // value as its argument. // 2. Let tv be ToPrimitive(O, hint Number). // 3. If tv is a Number and is not finite, return null. // XXX // 4. Let toISO be the result of calling the [[Get]] internal method of // O with argument "toISOString". // 5. If IsCallable(toISO) is false, throw a TypeError exception. if (typeof this.toISOString != "function") throw new TypeError(); // 6. Return the result of calling the [[Call]] internal method of // toISO with O as the this value and an empty argument list. return this.toISOString(); // NOTE 1 The argument is ignored. // NOTE 2 The toJSON function is intentionally generic; it does not // require that its this value be a Date object. Therefore, it can be // transferred to other kinds of objects for use as a method. However, // it does require that any such object have a toISOString method. An // object is free to use the argument key to filter its // stringification. }; } // 15.9.4.2 Date.parse (string) // 15.9.1.15 Date Time String Format // Date.parse // based on work shared by Daniel Friesen (dantman) // http://gist.github.com/303249 if (isNaN(Date.parse("T00:00"))) { // XXX global assignment won't work in embeddings that use // an alternate object for the context. Date = (function(NativeDate) { // Date.length === 7 var Date = function(Y, M, D, h, m, s, ms) { var length = arguments.length; if (this instanceof NativeDate) { var date = length === 1 && String(Y) === Y ? // isString(Y) // We explicitly pass it through parse: new NativeDate(Date.parse(Y)) : // We have to manually make calls depending on argument // length here length >= 7 ? new NativeDate(Y, M, D, h, m, s, ms) : length >= 6 ? new NativeDate(Y, M, D, h, m, s) : length >= 5 ? new NativeDate(Y, M, D, h, m) : length >= 4 ? new NativeDate(Y, M, D, h) : length >= 3 ? new NativeDate(Y, M, D) : length >= 2 ? new NativeDate(Y, M) : length >= 1 ? new NativeDate(Y) : new NativeDate(); // Prevent mixups with unfixed Date object date.constructor = Date; return date; } return NativeDate.apply(this, arguments); }; // 15.9.1.15 Date Time String Format var isoDateExpression = new RegExp("^" + "(?:" + // optional year-month-day "(" + // year capture "(?:[+-]\\d\\d)?" + // 15.9.1.15.1 Extended years "\\d\\d\\d\\d" + // four-digit year ")" + "(?:-" + // optional month-day "(\\d\\d)" + // month capture "(?:-" + // optional day "(\\d\\d)" + // day capture ")?" + ")?" + ")?" + "(?:T" + // hour:minute:second.subsecond "(\\d\\d)" + // hour capture ":(\\d\\d)" + // minute capture "(?::" + // optional :second.subsecond "(\\d\\d)" + // second capture "(?:\\.(\\d\\d\\d))?" + // milisecond capture ")?" + ")?" + "(?:" + // time zone "Z|" + // UTC capture "([+-])(\\d\\d):(\\d\\d)" + // timezone offset // capture sign, hour, minute ")?" + "$"); // Copy any custom methods a 3rd party library may have added for (var key in NativeDate) Date[key] = NativeDate[key]; // Copy "native" methods explicitly; they may be non-enumerable Date.now = NativeDate.now; Date.UTC = NativeDate.UTC; Date.prototype = NativeDate.prototype; Date.prototype.constructor = Date; // Upgrade Date.parse to handle the ISO dates we use // TODO review specification to ascertain whether it is // necessary to implement partial ISO date strings. Date.parse = function(string) { var match = isoDateExpression.exec(string); if (match) { match.shift(); // kill match[0], the full match // recognize times without dates before normalizing the // numeric values, for later use var timeOnly = match[0] === undefined; // parse numerics for (var i = 0; i < 10; i++) { // skip + or - for the timezone offset if (i === 7) continue; // Note: parseInt would read 0-prefix numbers as // octal. Number constructor or unary + work better // here: match[i] = +(match[i] || (i < 3 ? 1 : 0)); // match[1] is the month. Months are 0-11 in JavaScript // Date objects, but 1-12 in ISO notation, so we // decrement. if (i === 1) match[i]--; } // if no year-month-date is provided, return a milisecond // quantity instead of a UTC date number value. if (timeOnly) return ((match[3] * 60 + match[4]) * 60 + match[5]) * 1000 + match[6]; // account for an explicit time zone offset if provided var offset = (match[8] * 60 + match[9]) * 60 * 1000; if (match[6] === "-") offset = -offset; return NativeDate.UTC.apply(this, match.slice(0, 7)) + offset; } return NativeDate.parse.apply(this, arguments); }; return Date; })(Date); } // // Function // ======== // // ES-5 15.3.4.5 // http://www.ecma-international.org/publications/files/drafts/tc39-2009-025.pdf var slice = Array.prototype.slice; if (!Function.prototype.bind) { Function.prototype.bind = function (that) { // .length is 1 // 1. Let Target be the this value. var target = this; // 2. If IsCallable(Target) is false, throw a TypeError exception. // XXX this gets pretty close, for all intents and purposes, letting // some duck-types slide if (typeof target.apply != "function" || typeof target.call != "function") return new TypeError(); // 3. Let A be a new (possibly empty) internal list of all of the // argument values provided after thisArg (arg1, arg2 etc), in order. var args = slice.call(arguments); // 4. Let F be a new native ECMAScript object. // 9. Set the [[Prototype]] internal property of F to the standard // built-in Function prototype object as specified in 15.3.3.1. // 10. Set the [[Call]] internal property of F as described in // 15.3.4.5.1. // 11. Set the [[Construct]] internal property of F as described in // 15.3.4.5.2. // 12. Set the [[HasInstance]] internal property of F as described in // 15.3.4.5.3. // 13. The [[Scope]] internal property of F is unused and need not // exist. var bound = function () { if (this instanceof bound) { // 15.3.4.5.2 [[Construct]] // When the [[Construct]] internal method of a function object, // F that was created using the bind function is called with a // list of arguments ExtraArgs the following steps are taken: // 1. Let target be the value of F's [[TargetFunction]] // internal property. // 2. If target has no [[Construct]] internal method, a // TypeError exception is thrown. // 3. Let boundArgs be the value of F's [[BoundArgs]] internal // property. // 4. Let args be a new list containing the same values as the // list boundArgs in the same order followed by the same // values as the list ExtraArgs in the same order. var self = Object.create(target.prototype); target.apply(self, args.concat(slice.call(arguments))); return self; } else { // 15.3.4.5.1 [[Call]] // When the [[Call]] internal method of a function object, F, // which was created using the bind function is called with a // this value and a list of arguments ExtraArgs the following // steps are taken: // 1. Let boundArgs be the value of F's [[BoundArgs]] internal // property. // 2. Let boundThis be the value of F's [[BoundThis]] internal // property. // 3. Let target be the value of F's [[TargetFunction]] internal // property. // 4. Let args be a new list containing the same values as the list // boundArgs in the same order followed by the same values as // the list ExtraArgs in the same order. 5. Return the // result of calling the [[Call]] internal method of target // providing boundThis as the this value and providing args // as the arguments. // equiv: target.call(this, ...boundArgs, ...args) return target.call.apply( target, args.concat(slice.call(arguments)) ); } }; // 5. Set the [[TargetFunction]] internal property of F to Target. // extra: bound.bound = target; // 6. Set the [[BoundThis]] internal property of F to the value of // thisArg. // extra: bound.boundTo = that; // 7. Set the [[BoundArgs]] internal property of F to A. // extra: bound.boundArgs = args; bound.length = ( // 14. If the [[Class]] internal property of Target is "Function", then typeof target == "function" ? // a. Let L be the length property of Target minus the length of A. // b. Set the length own property of F to either 0 or L, whichever is larger. Math.max(target.length - args.length, 0) : // 15. Else set the length own property of F to 0. 0 ) // 16. The length own property of F is given attributes as specified in // 15.3.5.1. // TODO // 17. Set the [[Extensible]] internal property of F to true. // TODO // 18. Call the [[DefineOwnProperty]] internal method of F with // arguments "caller", PropertyDescriptor {[[Value]]: null, // [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: // false}, and false. // TODO // 19. Call the [[DefineOwnProperty]] internal method of F with // arguments "arguments", PropertyDescriptor {[[Value]]: null, // [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: // false}, and false. // TODO // NOTE Function objects created using Function.prototype.bind do not // have a prototype property. // XXX can't delete it in pure-js. return bound; }; } // // String // ====== // // ES5 15.5.4.20 if (!String.prototype.trim) { // http://blog.stevenlevithan.com/archives/faster-trim-javascript var trimBeginRegexp = /^\s\s*/; var trimEndRegexp = /\s\s*$/; String.prototype.trim = function () { return String(this).replace(trimBeginRegexp, '').replace(trimEndRegexp, ''); }; } exports.globalsLoaded = true; });/* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Mozilla Skywriter. * * The Initial Developer of the Original Code is * Mozilla. * Portions created by the Initial Developer are Copyright (C) 2009 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Kevin Dangoor (kdangoor@mozilla.com) * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('pilot/index', function(require, exports, module) { exports.startup = function(data, reason) { require('pilot/fixoldbrowsers'); require('pilot/types/basic').startup(data, reason); require('pilot/types/command').startup(data, reason); require('pilot/types/settings').startup(data, reason); require('pilot/commands/settings').startup(data, reason); require('pilot/commands/basic').startup(data, reason); // require('pilot/commands/history').startup(data, reason); require('pilot/settings/canon').startup(data, reason); require('pilot/canon').startup(data, reason); }; exports.shutdown = function(data, reason) { require('pilot/types/basic').shutdown(data, reason); require('pilot/types/command').shutdown(data, reason); require('pilot/types/settings').shutdown(data, reason); require('pilot/commands/settings').shutdown(data, reason); require('pilot/commands/basic').shutdown(data, reason); // require('pilot/commands/history').shutdown(data, reason); require('pilot/settings/canon').shutdown(data, reason); require('pilot/canon').shutdown(data, reason); }; }); /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Mozilla Skywriter. * * The Initial Developer of the Original Code is * Mozilla. * Portions created by the Initial Developer are Copyright (C) 2009 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Joe Walker (jwalker@mozilla.com) * Kevin Dangoor (kdangoor@mozilla.com) * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('pilot/types/basic', function(require, exports, module) { var types = require("pilot/types"); var Type = types.Type; var Conversion = types.Conversion; var Status = types.Status; /** * These are the basic types that we accept. They are vaguely based on the * Jetpack settings system (https://wiki.mozilla.org/Labs/Jetpack/JEP/24) * although clearly more restricted. * *

In addition to these types, Jetpack also accepts range, member, password * that we are thinking of adding. * *

This module probably should not be accessed directly, but instead used * through types.js */ /** * 'text' is the default if no type is given. */ var text = new Type(); text.stringify = function(value) { return value; }; text.parse = function(value) { if (typeof value != 'string') { throw new Error('non-string passed to text.parse()'); } return new Conversion(value); }; text.name = 'text'; /** * We don't currently plan to distinguish between integers and floats */ var number = new Type(); number.stringify = function(value) { if (!value) { return null; } return '' + value; }; number.parse = function(value) { if (typeof value != 'string') { throw new Error('non-string passed to number.parse()'); } if (value.replace(/\s/g, '').length === 0) { return new Conversion(null, Status.INCOMPLETE, ''); } var reply = new Conversion(parseInt(value, 10)); if (isNaN(reply.value)) { reply.status = Status.INVALID; reply.message = 'Can\'t convert "' + value + '" to a number.'; } return reply; }; number.decrement = function(value) { return value - 1; }; number.increment = function(value) { return value + 1; }; number.name = 'number'; /** * One of a known set of options */ function SelectionType(typeSpec) { if (!Array.isArray(typeSpec.data) && typeof typeSpec.data !== 'function') { throw new Error('instances of SelectionType need typeSpec.data to be an array or function that returns an array:' + JSON.stringify(typeSpec)); } Object.keys(typeSpec).forEach(function(key) { this[key] = typeSpec[key]; }, this); }; SelectionType.prototype = new Type(); SelectionType.prototype.stringify = function(value) { return value; }; SelectionType.prototype.parse = function(str) { if (typeof str != 'string') { throw new Error('non-string passed to parse()'); } if (!this.data) { throw new Error('Missing data on selection type extension.'); } var data = (typeof(this.data) === 'function') ? this.data() : this.data; // The matchedValue could be the boolean value false var hasMatched = false; var matchedValue; var completions = []; data.forEach(function(option) { if (str == option) { matchedValue = this.fromString(option); hasMatched = true; } else if (option.indexOf(str) === 0) { completions.push(this.fromString(option)); } }, this); if (hasMatched) { return new Conversion(matchedValue); } else { // This is something of a hack it basically allows us to tell the // setting type to forget its last setting hack. if (this.noMatch) { this.noMatch(); } if (completions.length > 0) { var msg = 'Possibilities' + (str.length === 0 ? '' : ' for \'' + str + '\''); return new Conversion(null, Status.INCOMPLETE, msg, completions); } else { var msg = 'Can\'t use \'' + str + '\'.'; return new Conversion(null, Status.INVALID, msg, completions); } } }; SelectionType.prototype.fromString = function(str) { return str; }; SelectionType.prototype.decrement = function(value) { var data = (typeof this.data === 'function') ? this.data() : this.data; var index; if (value == null) { index = data.length - 1; } else { var name = this.stringify(value); var index = data.indexOf(name); index = (index === 0 ? data.length - 1 : index - 1); } return this.fromString(data[index]); }; SelectionType.prototype.increment = function(value) { var data = (typeof this.data === 'function') ? this.data() : this.data; var index; if (value == null) { index = 0; } else { var name = this.stringify(value); var index = data.indexOf(name); index = (index === data.length - 1 ? 0 : index + 1); } return this.fromString(data[index]); }; SelectionType.prototype.name = 'selection'; /** * SelectionType is a base class for other types */ exports.SelectionType = SelectionType; /** * true/false values */ var bool = new SelectionType({ name: 'bool', data: [ 'true', 'false' ], stringify: function(value) { return '' + value; }, fromString: function(str) { return str === 'true' ? true : false; } }); /** * A we don't know right now, but hope to soon. */ function DeferredType(typeSpec) { if (typeof typeSpec.defer !== 'function') { throw new Error('Instances of DeferredType need typeSpec.defer to be a function that returns a type'); } Object.keys(typeSpec).forEach(function(key) { this[key] = typeSpec[key]; }, this); }; DeferredType.prototype = new Type(); DeferredType.prototype.stringify = function(value) { return this.defer().stringify(value); }; DeferredType.prototype.parse = function(value) { return this.defer().parse(value); }; DeferredType.prototype.decrement = function(value) { var deferred = this.defer(); return (deferred.decrement ? deferred.decrement(value) : undefined); }; DeferredType.prototype.increment = function(value) { var deferred = this.defer(); return (deferred.increment ? deferred.increment(value) : undefined); }; DeferredType.prototype.name = 'deferred'; /** * DeferredType is a base class for other types */ exports.DeferredType = DeferredType; /** * A set of objects of the same type */ function ArrayType(typeSpec) { if (typeSpec instanceof Type) { this.subtype = typeSpec; } else if (typeof typeSpec === 'string') { this.subtype = types.getType(typeSpec); if (this.subtype == null) { throw new Error('Unknown array subtype: ' + typeSpec); } } else { throw new Error('Can\' handle array subtype'); } }; ArrayType.prototype = new Type(); ArrayType.prototype.stringify = function(values) { // TODO: Check for strings with spaces and add quotes return values.join(' '); }; ArrayType.prototype.parse = function(value) { return this.defer().parse(value); }; ArrayType.prototype.name = 'array'; /** * Registration and de-registration. */ var isStarted = false; exports.startup = function() { if (isStarted) { return; } isStarted = true; types.registerType(text); types.registerType(number); types.registerType(bool); types.registerType(SelectionType); types.registerType(DeferredType); types.registerType(ArrayType); }; exports.shutdown = function() { isStarted = false; types.unregisterType(text); types.unregisterType(number); types.unregisterType(bool); types.unregisterType(SelectionType); types.unregisterType(DeferredType); types.unregisterType(ArrayType); }; }); /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Skywriter. * * The Initial Developer of the Original Code is * Mozilla. * Portions created by the Initial Developer are Copyright (C) 2009 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Joe Walker (jwalker@mozilla.com) * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('pilot/types', function(require, exports, module) { /** * Some types can detect validity, that is to say they can distinguish between * valid and invalid values. * TODO: Change these constants to be numbers for more performance? */ var Status = { /** * The conversion process worked without any problem, and the value is * valid. There are a number of failure states, so the best way to check * for failure is (x !== Status.VALID) */ VALID: { toString: function() { return 'VALID'; }, valueOf: function() { return 0; } }, /** * A conversion process failed, however it was noted that the string * provided to 'parse()' could be VALID by the addition of more characters, * so the typing may not be actually incorrect yet, just unfinished. * @see Status.INVALID */ INCOMPLETE: { toString: function() { return 'INCOMPLETE'; }, valueOf: function() { return 1; } }, /** * The conversion process did not work, the value should be null and a * reason for failure should have been provided. In addition some completion * values may be available. * @see Status.INCOMPLETE */ INVALID: { toString: function() { return 'INVALID'; }, valueOf: function() { return 2; } }, /** * A combined status is the worser of the provided statuses */ combine: function(statuses) { var combined = Status.VALID; for (var i = 0; i < statuses.length; i++) { if (statuses[i].valueOf() > combined.valueOf()) { combined = statuses[i]; } } return combined; } }; exports.Status = Status; /** * The type.parse() method returns a Conversion to inform the user about not * only the result of a Conversion but also about what went wrong. * We could use an exception, and throw if the conversion failed, but that * seems to violate the idea that exceptions should be exceptional. Typos are * not. Also in order to store both a status and a message we'd still need * some sort of exception type... */ function Conversion(value, status, message, predictions) { /** * The result of the conversion process. Will be null if status != VALID */ this.value = value; /** * The status of the conversion. * @see Status */ this.status = status || Status.VALID; /** * A message to go with the conversion. This could be present for any status * including VALID in the case where we want to note a warning for example. * I18N: On the one hand this nasty and un-internationalized, however with * a command line it is hard to know where to start. */ this.message = message; /** * A array of strings which are the systems best guess at better inputs than * the one presented. * We generally expect there to be about 7 predictions (to match human list * comprehension ability) however it is valid to provide up to about 20, * or less. It is the job of the predictor to decide a smart cut-off. * For example if there are 4 very good matches and 4 very poor ones, * probably only the 4 very good matches should be presented. */ this.predictions = predictions || []; } exports.Conversion = Conversion; /** * Most of our types are 'static' e.g. there is only one type of 'text', however * some types like 'selection' and 'deferred' are customizable. The basic * Type type isn't useful, but does provide documentation about what types do. */ function Type() { }; Type.prototype = { /** * Convert the given value to a string representation. * Where possible, there should be round-tripping between values and their * string representations. */ stringify: function(value) { throw new Error("not implemented"); }, /** * Convert the given str to an instance of this type. * Where possible, there should be round-tripping between values and their * string representations. * @return Conversion */ parse: function(str) { throw new Error("not implemented"); }, /** * The plug-in system, and other things need to know what this type is * called. The name alone is not enough to fully specify a type. Types like * 'selection' and 'deferred' need extra data, however this function returns * only the name, not the extra data. *

In old bespin, equality was based on the name. This may turn out to be * important in Ace too. */ name: undefined, /** * If there is some concept of a higher value, return it, * otherwise return undefined. */ increment: function(value) { return undefined; }, /** * If there is some concept of a lower value, return it, * otherwise return undefined. */ decrement: function(value) { return undefined; }, /** * There is interesting information (like predictions) in a conversion of * nothing, the output of this can sometimes be customized. * @return Conversion */ getDefault: function() { return this.parse(''); } }; exports.Type = Type; /** * Private registry of types * Invariant: types[name] = type.name */ var types = {}; /** * Add a new type to the list available to the system. * You can pass 2 things to this function - either an instance of Type, in * which case we return this instance when #getType() is called with a 'name' * that matches type.name. * Also you can pass in a constructor (i.e. function) in which case when * #getType() is called with a 'name' that matches Type.prototype.name we will * pass the typeSpec into this constructor. See #reconstituteType(). */ exports.registerType = function(type) { if (typeof type === 'object') { if (type instanceof Type) { if (!type.name) { throw new Error('All registered types must have a name'); } types[type.name] = type; } else { throw new Error('Can\'t registerType using: ' + type); } } else if (typeof type === 'function') { if (!type.prototype.name) { throw new Error('All registered types must have a name'); } types[type.prototype.name] = type; } else { throw new Error('Unknown type: ' + type); } }; exports.registerTypes = function registerTypes(types) { Object.keys(types).forEach(function (name) { var type = types[name]; type.name = name; exports.registerType(type); }); }; /** * Remove a type from the list available to the system */ exports.deregisterType = function(type) { delete types[type.name]; }; /** * See description of #exports.registerType() */ function reconstituteType(name, typeSpec) { if (name.substr(-2) === '[]') { // i.e. endsWith('[]') var subtypeName = name.slice(0, -2); return new types['array'](subtypeName); } var type = types[name]; if (typeof type === 'function') { type = new type(typeSpec); } return type; } /** * Find a type, previously registered using #registerType() */ exports.getType = function(typeSpec) { if (typeof typeSpec === 'string') { return reconstituteType(typeSpec); } if (typeof typeSpec === 'object') { if (!typeSpec.name) { throw new Error('Missing \'name\' member to typeSpec'); } return reconstituteType(typeSpec.name, typeSpec); } throw new Error('Can\'t extract type from ' + typeSpec); }; }); /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Mozilla Skywriter. * * The Initial Developer of the Original Code is * Mozilla. * Portions created by the Initial Developer are Copyright (C) 2009 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Joe Walker (jwalker@mozilla.com) * Kevin Dangoor (kdangoor@mozilla.com) * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('pilot/types/command', function(require, exports, module) { var canon = require("pilot/canon"); var SelectionType = require("pilot/types/basic").SelectionType; var types = require("pilot/types"); /** * Select from the available commands */ var command = new SelectionType({ name: 'command', data: function() { return canon.getCommandNames(); }, stringify: function(command) { return command.name; }, fromString: function(str) { return canon.getCommand(str); } }); /** * Registration and de-registration. */ exports.startup = function() { types.registerType(command); }; exports.shutdown = function() { types.unregisterType(command); }; }); /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Mozilla Skywriter. * * The Initial Developer of the Original Code is * Mozilla. * Portions created by the Initial Developer are Copyright (C) 2009 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Joe Walker (jwalker@mozilla.com) * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('pilot/canon', function(require, exports, module) { var console = require('pilot/console'); var Trace = require('pilot/stacktrace').Trace; var oop = require('pilot/oop'); var useragent = require('pilot/useragent'); var keyUtil = require('pilot/keys'); var EventEmitter = require('pilot/event_emitter').EventEmitter; var typecheck = require('pilot/typecheck'); var catalog = require('pilot/catalog'); var Status = require('pilot/types').Status; var types = require('pilot/types'); var lang = require('pilot/lang'); /* // TODO: this doesn't belong here - or maybe anywhere? var dimensionsChangedExtensionSpec = { name: 'dimensionsChanged', description: 'A dimensionsChanged is a way to be notified of ' + 'changes to the dimension of Skywriter' }; exports.startup = function(data, reason) { catalog.addExtensionSpec(commandExtensionSpec); }; exports.shutdown = function(data, reason) { catalog.removeExtensionSpec(commandExtensionSpec); }; */ var commandExtensionSpec = { name: 'command', description: 'A command is a bit of functionality with optional ' + 'typed arguments which can do something small like moving ' + 'the cursor around the screen, or large like cloning a ' + 'project from VCS.', indexOn: 'name' }; exports.startup = function(data, reason) { // TODO: this is probably all kinds of evil, but we need something working catalog.addExtensionSpec(commandExtensionSpec); }; exports.shutdown = function(data, reason) { catalog.removeExtensionSpec(commandExtensionSpec); }; /** * Manage a list of commands in the current canon */ /** * A Command is a discrete action optionally with a set of ways to customize * how it happens. This is here for documentation purposes. * TODO: Document better */ var thingCommand = { name: 'thing', description: 'thing is an example command', params: [{ name: 'param1', description: 'an example parameter', type: 'text', defaultValue: null }], exec: function(env, args, request) { thing(); } }; /** * A lookup hash of our registered commands */ var commands = {}; /** * A lookup has for command key bindings that use a string as sender. */ var commmandKeyBinding = {}; /** * Array with command key bindings that use a function to determ the sender. */ var commandKeyBindingFunc = { }; function splitSafe(s, separator, limit, bLowerCase) { return (bLowerCase && s.toLowerCase() || s) .replace(/(?:^\s+|\n|\s+$)/g, "") .split(new RegExp("[\\s ]*" + separator + "[\\s ]*", "g"), limit || 999); } function parseKeys(keys, val, ret) { var key, hashId = 0, parts = splitSafe(keys, "\\-", null, true), i = 0, l = parts.length; for (; i < l; ++i) { if (keyUtil.KEY_MODS[parts[i]]) hashId = hashId | keyUtil.KEY_MODS[parts[i]]; else key = parts[i] || "-"; //when empty, the splitSafe removed a '-' } if (ret == null) { return { key: key, hashId: hashId } } else { (ret[hashId] || (ret[hashId] = {}))[key] = val; } } var platform = useragent.isMac ? "mac" : "win"; function buildKeyHash(command) { var binding = command.bindKey, key = binding[platform], ckb = commmandKeyBinding, ckbf = commandKeyBindingFunc if (!binding.sender) { throw new Error('All key bindings must have a sender'); } if (!binding.mac && binding.mac !== null) { throw new Error('All key bindings must have a mac key binding'); } if (!binding.win && binding.win !== null) { throw new Error('All key bindings must have a windows key binding'); } if(!binding[platform]) { // No keymapping for this platform. return; } if (typeof binding.sender == 'string') { var targets = splitSafe(binding.sender, "\\|", null, true); targets.forEach(function(target) { if (!ckb[target]) { ckb[target] = { }; } key.split("|").forEach(function(keyPart) { parseKeys(keyPart, command, ckb[target]); }); }); } else if (typecheck.isFunction(binding.sender)) { var val = { command: command, sender: binding.sender }; keyData = parseKeys(key); if (!ckbf[keyData.hashId]) { ckbf[keyData.hashId] = { }; } if (!ckbf[keyData.hashId][keyData.key]) { ckbf[keyData.hashId][keyData.key] = [ val ]; } else { ckbf[keyData.hashId][keyData.key].push(val); } } else { throw new Error('Key binding must have a sender that is a string or function'); } } function findKeyCommand(env, sender, hashId, textOrKey) { // Convert keyCode to the string representation. if (typecheck.isNumber(textOrKey)) { textOrKey = keyUtil.keyCodeToString(textOrKey); } // Check bindings with functions as sender first. var bindFuncs = (commandKeyBindingFunc[hashId] || {})[textOrKey] || []; for (var i = 0; i < bindFuncs.length; i++) { if (bindFuncs[i].sender(env, sender, hashId, textOrKey)) { return bindFuncs[i].command; } } var ckbr = commmandKeyBinding[sender] return ckbr && ckbr[hashId] && ckbr[hashId][textOrKey]; } function execKeyCommand(env, sender, hashId, textOrKey) { var command = findKeyCommand(env, sender, hashId, textOrKey); if (command) { return exec(command, env, sender, { }); } else { return false; } } /** * A sorted list of command names, we regularly want them in order, so pre-sort */ var commandNames = []; /** * This registration method isn't like other Ace registration methods because * it doesn't return a decorated command because there is no functional * decoration to be done. * TODO: Are we sure that in the future there will be no such decoration? */ function addCommand(command) { if (!command.name) { throw new Error('All registered commands must have a name'); } if (command.params == null) { command.params = []; } if (!Array.isArray(command.params)) { throw new Error('command.params must be an array in ' + command.name); } // Replace the type command.params.forEach(function(param) { if (!param.name) { throw new Error('In ' + command.name + ': all params must have a name'); } upgradeType(command.name, param); }, this); commands[command.name] = command; if (command.bindKey) { buildKeyHash(command); } commandNames.push(command.name); commandNames.sort(); }; function upgradeType(name, param) { var lookup = param.type; param.type = types.getType(lookup); if (param.type == null) { throw new Error('In ' + name + '/' + param.name + ': can\'t find type for: ' + JSON.stringify(lookup)); } } function removeCommand(command) { var name = (typeof command === 'string' ? command : command.name); delete commands[name]; lang.arrayRemove(commandNames, name); }; function getCommand(name) { return commands[name]; }; function getCommandNames() { return commandNames; }; /** * Default ArgumentProvider that is used if no ArgumentProvider is provided * by the command's sender. */ function defaultArgsProvider(request, callback) { var args = request.args, params = request.command.params; for (var i = 0; i < params.length; i++) { var param = params[i]; // If the parameter is already valid, then don't ask for it anymore. if (request.getParamStatus(param) != Status.VALID || // Ask for optional parameters as well. param.defaultValue === null) { var paramPrompt = param.description; if (param.defaultValue === null) { paramPrompt += " (optional)"; } var value = prompt(paramPrompt, param.defaultValue || ""); // No value but required -> nope. if (!value) { callback(); return; } else { args[param.name] = value; } } } callback(); } /** * Entry point for keyboard accelerators or anything else that wants to execute * a command. A new request object is created and a check performed, if the * passed in arguments are VALID/INVALID or INCOMPLETE. If they are INCOMPLETE * the ArgumentProvider on the sender is called or otherwise the default * ArgumentProvider to get the still required arguments. * If they are valid (or valid after the ArgumentProvider is done), the command * is executed. * * @param command Either a command, or the name of one * @param env Current environment to execute the command in * @param sender String that should be the same as the senderObject stored on * the environment in env[sender] * @param args Arguments for the command * @param typed (Optional) */ function exec(command, env, sender, args, typed) { if (typeof command === 'string') { command = commands[command]; } if (!command) { // TODO: Should we complain more than returning false? return false; } var request = new Request({ sender: sender, command: command, args: args || {}, typed: typed }); /** * Executes the command and ensures request.done is called on the request in * case it's not marked to be done already or async. */ function execute() { command.exec(env, request.args, request); // If the request isn't asnync and isn't done, then make it done. if (!request.isAsync && !request.isDone) { request.done(); } } if (request.getStatus() == Status.INVALID) { console.error("Canon.exec: Invalid parameter(s) passed to " + command.name); return false; } // If the request isn't complete yet, try to complete it. else if (request.getStatus() == Status.INCOMPLETE) { // Check if the sender has a ArgsProvider, otherwise use the default // build in one. var argsProvider; var senderObj = env[sender]; if (!senderObj || !senderObj.getArgsProvider || !(argsProvider = senderObj.getArgsProvider())) { argsProvider = defaultArgsProvider; } // Ask the paramProvider to complete the request. argsProvider(request, function() { if (request.getStatus() == Status.VALID) { execute(); } }); return true; } else { execute(); return true; } }; exports.removeCommand = removeCommand; exports.addCommand = addCommand; exports.getCommand = getCommand; exports.getCommandNames = getCommandNames; exports.findKeyCommand = findKeyCommand; exports.exec = exec; exports.execKeyCommand = execKeyCommand; exports.upgradeType = upgradeType; /** * We publish a 'output' event whenever new command begins output * TODO: make this more obvious */ oop.implement(exports, EventEmitter); /** * Current requirements are around displaying the command line, and provision * of a 'history' command and cursor up|down navigation of history. *

Future requirements could include: *

    *
  • Multiple command lines *
  • The ability to recall key presses (i.e. requests with no output) which * will likely be needed for macro recording or similar *
  • The ability to store the command history either on the server or in the * browser local storage. *
*

The execute() command doesn't really live here, except as part of that * last future requirement, and because it doesn't really have anywhere else to * live. */ /** * The array of requests that wish to announce their presence */ var requests = []; /** * How many requests do we store? */ var maxRequestLength = 100; /** * To create an invocation, you need to do something like this (all the ctor * args are optional): *

 * var request = new Request({
 *     command: command,
 *     args: args,
 *     typed: typed
 * });
 * 
* @constructor */ function Request(options) { options = options || {}; // Will be used in the keyboard case and the cli case this.command = options.command; // Will be used only in the cli case this.args = options.args; this.typed = options.typed; // Have we been initialized? this._begunOutput = false; this.start = new Date(); this.end = null; this.completed = false; this.error = false; }; oop.implement(Request.prototype, EventEmitter); /** * Return the status of a parameter on the request object. */ Request.prototype.getParamStatus = function(param) { var args = this.args || {}; // Check if there is already a value for this parameter. if (param.name in args) { // If there is no value set and then the value is VALID if it's not // required or INCOMPLETE if not set yet. if (args[param.name] == null) { if (param.defaultValue === null) { return Status.VALID; } else { return Status.INCOMPLETE; } } // Check if the parameter value is valid. var reply, // The passed in value when parsing a type is a string. argsValue = args[param.name].toString(); // Type.parse can throw errors. try { reply = param.type.parse(argsValue); } catch (e) { return Status.INVALID; } if (reply.status != Status.VALID) { return reply.status; } } // Check if the param is marked as required. else if (param.defaultValue === undefined) { // The parameter is not set on the args object but it's required, // which means, things are invalid. return Status.INCOMPLETE; } return Status.VALID; } /** * Return the status of a parameter name on the request object. */ Request.prototype.getParamNameStatus = function(paramName) { var params = this.command.params || []; for (var i = 0; i < params.length; i++) { if (params[i].name == paramName) { return this.getParamStatus(params[i]); } } throw "Parameter '" + paramName + "' not defined on command '" + this.command.name + "'"; } /** * Checks if all required arguments are set on the request such that it can * get executed. */ Request.prototype.getStatus = function() { var args = this.args || {}, params = this.command.params; // If there are not parameters, then it's valid. if (!params || params.length == 0) { return Status.VALID; } var status = []; for (var i = 0; i < params.length; i++) { status.push(this.getParamStatus(params[i])); } return Status.combine(status); } /** * Lazy init to register with the history should only be done on output. * init() is expensive, and won't be used in the majority of cases */ Request.prototype._beginOutput = function() { this._begunOutput = true; this.outputs = []; requests.push(this); // This could probably be optimized with some maths, but 99.99% of the // time we will only be off by one, and I'm feeling lazy. while (requests.length > maxRequestLength) { requests.shiftObject(); } exports._dispatchEvent('output', { requests: requests, request: this }); }; /** * Sugar for: *
request.error = true; request.done(output);
*/ Request.prototype.doneWithError = function(content) { this.error = true; this.done(content); }; /** * Declares that this function will not be automatically done when * the command exits */ Request.prototype.async = function() { this.isAsync = true; if (!this._begunOutput) { this._beginOutput(); } }; /** * Complete the currently executing command with successful output. * @param output Either DOM node, an SproutCore element or something that * can be used in the content of a DIV to create a DOM node. */ Request.prototype.output = function(content) { if (!this._begunOutput) { this._beginOutput(); } if (typeof content !== 'string' && !(content instanceof Node)) { content = content.toString(); } this.outputs.push(content); this.isDone = true; this._dispatchEvent('output', {}); return this; }; /** * All commands that do output must call this to indicate that the command * has finished execution. */ Request.prototype.done = function(content) { this.completed = true; this.end = new Date(); this.duration = this.end.getTime() - this.start.getTime(); if (content) { this.output(content); } // Ensure to finish the request only once. if (!this.isDone) { this.isDone = true; this._dispatchEvent('output', {}); } }; exports.Request = Request; }); /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Mozilla Skywriter. * * The Initial Developer of the Original Code is * Mozilla. * Portions created by the Initial Developer are Copyright (C) 2009 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Joe Walker (jwalker@mozilla.com) * Patrick Walton (pwalton@mozilla.com) * Julian Viereck (jviereck@mozilla.com) * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('pilot/console', function(require, exports, module) { /** * This object represents a "safe console" object that forwards debugging * messages appropriately without creating a dependency on Firebug in Firefox. */ var noop = function() {}; // These are the functions that are available in Chrome 4/5, Safari 4 // and Firefox 3.6. Don't add to this list without checking browser support var NAMES = [ "assert", "count", "debug", "dir", "dirxml", "error", "group", "groupEnd", "info", "log", "profile", "profileEnd", "time", "timeEnd", "trace", "warn" ]; if (typeof(window) === 'undefined') { // We're in a web worker. Forward to the main thread so the messages // will show up. NAMES.forEach(function(name) { exports[name] = function() { var args = Array.prototype.slice.call(arguments); var msg = { op: 'log', method: name, args: args }; postMessage(JSON.stringify(msg)); }; }); } else { // For each of the console functions, copy them if they exist, stub if not NAMES.forEach(function(name) { if (window.console && window.console[name]) { exports[name] = Function.prototype.bind.call(window.console[name], window.console); } else { exports[name] = noop; } }); } }); define('pilot/stacktrace', function(require, exports, module) { var ua = require("pilot/useragent"); var console = require('pilot/console'); // Changed to suit the specific needs of running within Skywriter // Domain Public by Eric Wendelin http://eriwen.com/ (2008) // Luke Smith http://lucassmith.name/ (2008) // Loic Dachary (2008) // Johan Euphrosine (2008) // Øyvind Sean Kinsey http://kinsey.no/blog // // Information and discussions // http://jspoker.pokersource.info/skin/test-printstacktrace.html // http://eriwen.com/javascript/js-stack-trace/ // http://eriwen.com/javascript/stacktrace-update/ // http://pastie.org/253058 // http://browsershots.org/http://jspoker.pokersource.info/skin/test-printstacktrace.html // // // guessFunctionNameFromLines comes from firebug // // Software License Agreement (BSD License) // // Copyright (c) 2007, Parakey Inc. // All rights reserved. // // Redistribution and use of this software 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 Parakey Inc. nor the names of its // contributors may be used to endorse or promote products // derived from this software without specific prior // written permission of Parakey Inc. // // 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. /** * Different browsers create stack traces in different ways. * Feature Browser detection baby ;). */ var mode = (function() { // We use SC's browser detection here to avoid the "break on error" // functionality provided by Firebug. Firebug tries to do the right // thing here and break, but it happens every time you load the page. // bug 554105 if (ua.isGecko) { return 'firefox'; } else if (ua.isOpera) { return 'opera'; } else { return 'other'; } // SC doesn't do any detection of Chrome at this time. // this is the original feature detection code that is used as a // fallback. try { (0)(); } catch (e) { if (e.arguments) { return 'chrome'; } if (e.stack) { return 'firefox'; } if (window.opera && !('stacktrace' in e)) { //Opera 9- return 'opera'; } } return 'other'; })(); /** * */ function stringifyArguments(args) { for (var i = 0; i < args.length; ++i) { var argument = args[i]; if (typeof argument == 'object') { args[i] = '#object'; } else if (typeof argument == 'function') { args[i] = '#function'; } else if (typeof argument == 'string') { args[i] = '"' + argument + '"'; } } return args.join(','); } /** * Extract a stack trace from the format emitted by each browser. */ var decoders = { chrome: function(e) { var stack = e.stack; if (!stack) { console.log(e); return []; } return stack.replace(/^.*?\n/, ''). replace(/^.*?\n/, ''). replace(/^.*?\n/, ''). replace(/^[^\(]+?[\n$]/gm, ''). replace(/^\s+at\s+/gm, ''). replace(/^Object.\s*\(/gm, '{anonymous}()@'). split('\n'); }, firefox: function(e) { var stack = e.stack; if (!stack) { console.log(e); return []; } // stack = stack.replace(/^.*?\n/, ''); stack = stack.replace(/(?:\n@:0)?\s+$/m, ''); stack = stack.replace(/^\(/gm, '{anonymous}('); return stack.split('\n'); }, // Opera 7.x and 8.x only! opera: function(e) { var lines = e.message.split('\n'), ANON = '{anonymous}', lineRE = /Line\s+(\d+).*?script\s+(http\S+)(?:.*?in\s+function\s+(\S+))?/i, i, j, len; for (i = 4, j = 0, len = lines.length; i < len; i += 2) { if (lineRE.test(lines[i])) { lines[j++] = (RegExp.$3 ? RegExp.$3 + '()@' + RegExp.$2 + RegExp.$1 : ANON + '()@' + RegExp.$2 + ':' + RegExp.$1) + ' -- ' + lines[i + 1].replace(/^\s+/, ''); } } lines.splice(j, lines.length - j); return lines; }, // Safari, Opera 9+, IE, and others other: function(curr) { var ANON = '{anonymous}', fnRE = /function\s*([\w\-$]+)?\s*\(/i, stack = [], j = 0, fn, args; var maxStackSize = 10; while (curr && stack.length < maxStackSize) { fn = fnRE.test(curr.toString()) ? RegExp.$1 || ANON : ANON; args = Array.prototype.slice.call(curr['arguments']); stack[j++] = fn + '(' + stringifyArguments(args) + ')'; //Opera bug: if curr.caller does not exist, Opera returns curr (WTF) if (curr === curr.caller && window.opera) { //TODO: check for same arguments if possible break; } curr = curr.caller; } return stack; } }; /** * */ function NameGuesser() { } NameGuesser.prototype = { sourceCache: {}, ajax: function(url) { var req = this.createXMLHTTPObject(); if (!req) { return; } req.open('GET', url, false); req.setRequestHeader('User-Agent', 'XMLHTTP/1.0'); req.send(''); return req.responseText; }, createXMLHTTPObject: function() { // Try XHR methods in order and store XHR factory var xmlhttp, XMLHttpFactories = [ function() { return new XMLHttpRequest(); }, function() { return new ActiveXObject('Msxml2.XMLHTTP'); }, function() { return new ActiveXObject('Msxml3.XMLHTTP'); }, function() { return new ActiveXObject('Microsoft.XMLHTTP'); } ]; for (var i = 0; i < XMLHttpFactories.length; i++) { try { xmlhttp = XMLHttpFactories[i](); // Use memoization to cache the factory this.createXMLHTTPObject = XMLHttpFactories[i]; return xmlhttp; } catch (e) {} } }, getSource: function(url) { if (!(url in this.sourceCache)) { this.sourceCache[url] = this.ajax(url).split('\n'); } return this.sourceCache[url]; }, guessFunctions: function(stack) { for (var i = 0; i < stack.length; ++i) { var reStack = /{anonymous}\(.*\)@(\w+:\/\/([-\w\.]+)+(:\d+)?[^:]+):(\d+):?(\d+)?/; var frame = stack[i], m = reStack.exec(frame); if (m) { var file = m[1], lineno = m[4]; //m[7] is character position in Chrome if (file && lineno) { var functionName = this.guessFunctionName(file, lineno); stack[i] = frame.replace('{anonymous}', functionName); } } } return stack; }, guessFunctionName: function(url, lineNo) { try { return this.guessFunctionNameFromLines(lineNo, this.getSource(url)); } catch (e) { return 'getSource failed with url: ' + url + ', exception: ' + e.toString(); } }, guessFunctionNameFromLines: function(lineNo, source) { var reFunctionArgNames = /function ([^(]*)\(([^)]*)\)/; var reGuessFunction = /['"]?([0-9A-Za-z_]+)['"]?\s*[:=]\s*(function|eval|new Function)/; // Walk backwards from the first line in the function until we find the line which // matches the pattern above, which is the function definition var line = '', maxLines = 10; for (var i = 0; i < maxLines; ++i) { line = source[lineNo - i] + line; if (line !== undefined) { var m = reGuessFunction.exec(line); if (m) { return m[1]; } else { m = reFunctionArgNames.exec(line); } if (m && m[1]) { return m[1]; } } } return '(?)'; } }; var guesser = new NameGuesser(); var frameIgnorePatterns = [ /http:\/\/localhost:4020\/sproutcore.js:/ ]; exports.ignoreFramesMatching = function(regex) { frameIgnorePatterns.push(regex); }; /** * Create a stack trace from an exception * @param ex {Error} The error to create a stacktrace from (optional) * @param guess {Boolean} If we should try to resolve the names of anonymous functions */ exports.Trace = function Trace(ex, guess) { this._ex = ex; this._stack = decoders[mode](ex); if (guess) { this._stack = guesser.guessFunctions(this._stack); } }; /** * Log to the console a number of lines (default all of them) * @param lines {number} Maximum number of lines to wrote to console */ exports.Trace.prototype.log = function(lines) { if (lines <= 0) { // You aren't going to have more lines in your stack trace than this // and it still fits in a 32bit integer lines = 999999999; } var printed = 0; for (var i = 0; i < this._stack.length && printed < lines; i++) { var frame = this._stack[i]; var display = true; frameIgnorePatterns.forEach(function(regex) { if (regex.test(frame)) { display = false; } }); if (display) { console.debug(frame); printed++; } } }; }); /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Ajax.org Code Editor (ACE). * * The Initial Developer of the Original Code is * Ajax.org B.V. * Portions created by the Initial Developer are Copyright (C) 2010 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Fabian Jakobs * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('pilot/useragent', function(require, exports, module) { var os = (navigator.platform.match(/mac|win|linux/i) || ["other"])[0].toLowerCase(); var ua = navigator.userAgent; var av = navigator.appVersion; /** Is the user using a browser that identifies itself as Windows */ exports.isWin = (os == "win"); /** Is the user using a browser that identifies itself as Mac OS */ exports.isMac = (os == "mac"); /** Is the user using a browser that identifies itself as Linux */ exports.isLinux = (os == "linux"); exports.isIE = ! + "\v1"; /** Is this Firefox or related? */ exports.isGecko = exports.isMozilla = window.controllers && window.navigator.product === "Gecko"; /** oldGecko == rev < 2.0 **/ exports.isOldGecko = exports.isGecko && /rv\:1/.test(navigator.userAgent); /** Is this Opera */ exports.isOpera = window.opera && Object.prototype.toString.call(window.opera) == "[object Opera]"; /** Is the user using a browser that identifies itself as WebKit */ exports.isWebKit = parseFloat(ua.split("WebKit/")[1]) || undefined; exports.isAIR = ua.indexOf("AdobeAIR") >= 0; exports.isIPad = ua.indexOf("iPad") >= 0; /** * I hate doing this, but we need some way to determine if the user is on a Mac * The reason is that users have different expectations of their key combinations. * * Take copy as an example, Mac people expect to use CMD or APPLE + C * Windows folks expect to use CTRL + C */ exports.OS = { LINUX: 'LINUX', MAC: 'MAC', WINDOWS: 'WINDOWS' }; /** * Return an exports.OS constant */ exports.getOS = function() { if (exports.isMac) { return exports.OS['MAC']; } else if (exports.isLinux) { return exports.OS['LINUX']; } else { return exports.OS['WINDOWS']; } }; }); /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Ajax.org Code Editor (ACE). * * The Initial Developer of the Original Code is * Ajax.org B.V. * Portions created by the Initial Developer are Copyright (C) 2010 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Fabian Jakobs * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('pilot/oop', function(require, exports, module) { exports.inherits = (function() { var tempCtor = function() {}; return function(ctor, superCtor) { tempCtor.prototype = superCtor.prototype; ctor.super_ = superCtor.prototype; ctor.prototype = new tempCtor(); ctor.prototype.constructor = ctor; } }()); exports.mixin = function(obj, mixin) { for (var key in mixin) { obj[key] = mixin[key]; } }; exports.implement = function(proto, mixin) { exports.mixin(proto, mixin); }; }); /*! @license ========================================================================== SproutCore -- JavaScript Application Framework copyright 2006-2009, Sprout Systems Inc., Apple Inc. and contributors. 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. SproutCore and the SproutCore logo are trademarks of Sprout Systems, Inc. For more information about SproutCore, visit http://www.sproutcore.com ========================================================================== @license */ // Most of the following code is taken from SproutCore with a few changes. define('pilot/keys', function(require, exports, module) { var oop = require("pilot/oop"); /** * Helper functions and hashes for key handling. */ var Keys = (function() { var ret = { MODIFIER_KEYS: { 16: 'Shift', 17: 'Ctrl', 18: 'Alt', 224: 'Meta' }, KEY_MODS: { "ctrl": 1, "alt": 2, "option" : 2, "shift": 4, "meta": 8, "command": 8 }, FUNCTION_KEYS : { 8 : "Backspace", 9 : "Tab", 13 : "Return", 19 : "Pause", 27 : "Esc", 32 : "Space", 33 : "PageUp", 34 : "PageDown", 35 : "End", 36 : "Home", 37 : "Left", 38 : "Up", 39 : "Right", 40 : "Down", 44 : "Print", 45 : "Insert", 46 : "Delete", 112: "F1", 113: "F2", 114: "F3", 115: "F4", 116: "F5", 117: "F6", 118: "F7", 119: "F8", 120: "F9", 121: "F10", 122: "F11", 123: "F12", 144: "Numlock", 145: "Scrolllock" }, PRINTABLE_KEYS: { 32: ' ', 48: '0', 49: '1', 50: '2', 51: '3', 52: '4', 53: '5', 54: '6', 55: '7', 56: '8', 57: '9', 59: ';', 61: '=', 65: 'a', 66: 'b', 67: 'c', 68: 'd', 69: 'e', 70: 'f', 71: 'g', 72: 'h', 73: 'i', 74: 'j', 75: 'k', 76: 'l', 77: 'm', 78: 'n', 79: 'o', 80: 'p', 81: 'q', 82: 'r', 83: 's', 84: 't', 85: 'u', 86: 'v', 87: 'w', 88: 'x', 89: 'y', 90: 'z', 107: '+', 109: '-', 110: '.', 188: ',', 190: '.', 191: '/', 192: '`', 219: '[', 220: '\\', 221: ']', 222: '\"' } }; // A reverse map of FUNCTION_KEYS for (i in ret.FUNCTION_KEYS) { var name = ret.FUNCTION_KEYS[i].toUpperCase(); ret[name] = parseInt(i, 10); } // Add the MODIFIER_KEYS, FUNCTION_KEYS and PRINTABLE_KEYS to the KEY // variables as well. oop.mixin(ret, ret.MODIFIER_KEYS); oop.mixin(ret, ret.PRINTABLE_KEYS); oop.mixin(ret, ret.FUNCTION_KEYS); return ret; })(); oop.mixin(exports, Keys); exports.keyCodeToString = function(keyCode) { return (Keys[keyCode] || String.fromCharCode(keyCode)).toLowerCase(); } }); /* vim:ts=4:sts=4:sw=4: * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Ajax.org Code Editor (ACE). * * The Initial Developer of the Original Code is * Ajax.org B.V. * Portions created by the Initial Developer are Copyright (C) 2010 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Fabian Jakobs * Irakli Gozalishvili (http://jeditoolkit.com) * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('pilot/event_emitter', function(require, exports, module) { var EventEmitter = {}; EventEmitter._emit = EventEmitter._dispatchEvent = function(eventName, e) { this._eventRegistry = this._eventRegistry || {}; var listeners = this._eventRegistry[eventName]; if (!listeners || !listeners.length) return; var e = e || {}; e.type = eventName; for (var i=0; i * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('pilot/lang', function(require, exports, module) { exports.stringReverse = function(string) { return string.split("").reverse().join(""); }; exports.stringRepeat = function (string, count) { return new Array(count + 1).join(string); }; exports.copyObject = function(obj) { var copy = {}; for (var key in obj) { copy[key] = obj[key]; } return copy; }; exports.arrayToMap = function(arr) { var map = {}; for (var i=0; i (http://jeditoolkit.com) * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('pilot/settings', function(require, exports, module) { /** * This plug-in manages settings. */ var console = require('pilot/console'); var oop = require('pilot/oop'); var types = require('pilot/types'); var EventEmitter = require('pilot/event_emitter').EventEmitter; var catalog = require('pilot/catalog'); var settingExtensionSpec = { name: 'setting', description: 'A setting is something that the application offers as a ' + 'way to customize how it works', register: 'env.settings.addSetting', indexOn: 'name' }; exports.startup = function(data, reason) { catalog.addExtensionSpec(settingExtensionSpec); }; exports.shutdown = function(data, reason) { catalog.removeExtensionSpec(settingExtensionSpec); }; /** * Create a new setting. * @param settingSpec An object literal that looks like this: * { * name: 'thing', * description: 'Thing is an example setting', * type: 'string', * defaultValue: 'something' * } */ function Setting(settingSpec, settings) { this._settings = settings; Object.keys(settingSpec).forEach(function(key) { this[key] = settingSpec[key]; }, this); this.type = types.getType(this.type); if (this.type == null) { throw new Error('In ' + this.name + ': can\'t find type for: ' + JSON.stringify(settingSpec.type)); } if (!this.name) { throw new Error('Setting.name == undefined. Ignoring.', this); } if (!this.defaultValue === undefined) { throw new Error('Setting.defaultValue == undefined', this); } if (this.onChange) { this.on('change', this.onChange.bind(this)) } this.set(this.defaultValue); } Setting.prototype = { get: function() { return this.value; }, set: function(value) { if (this.value === value) { return; } this.value = value; if (this._settings.persister) { this._settings.persister.persistValue(this._settings, this.name, value); } this._dispatchEvent('change', { setting: this, value: value }); }, /** * Reset the value of the key setting to it's default */ resetValue: function() { this.set(this.defaultValue); } }; oop.implement(Setting.prototype, EventEmitter); /** * A base class for all the various methods of storing settings. *

Usage: *

 * // Create manually, or require 'settings' from the container.
 * // This is the manual version:
 * var settings = plugins.catalog.getObject('settings');
 * // Add a new setting
 * settings.addSetting({ name:'foo', ... });
 * // Display the default value
 * alert(settings.get('foo'));
 * // Alter the value, which also publishes the change etc.
 * settings.set('foo', 'bar');
 * // Reset the value to the default
 * settings.resetValue('foo');
 * 
* @constructor */ function Settings(persister) { // Storage for deactivated values this._deactivated = {}; // Storage for the active settings this._settings = {}; // We often want sorted setting names. Cache this._settingNames = []; if (persister) { this.setPersister(persister); } }; Settings.prototype = { /** * Function to add to the list of available settings. *

Example usage: *

     * var settings = plugins.catalog.getObject('settings');
     * settings.addSetting({
     *     name: 'tabsize', // For use in settings.get('X')
     *     type: 'number',  // To allow value checking.
     *     defaultValue: 4  // Default value for use when none is directly set
     * });
     * 
* @param {object} settingSpec Object containing name/type/defaultValue members. */ addSetting: function(settingSpec) { var setting = new Setting(settingSpec, this); this._settings[setting.name] = setting; this._settingNames.push(setting.name); this._settingNames.sort(); }, addSettings: function addSettings(settings) { Object.keys(settings).forEach(function (name) { var setting = settings[name]; if (!('name' in setting)) setting.name = name; this.addSetting(setting); }, this); }, removeSetting: function(setting) { var name = (typeof setting === 'string' ? setting : setting.name); setting = this._settings[name]; delete this._settings[name]; util.arrayRemove(this._settingNames, name); settings.removeAllListeners('change'); }, removeSettings: function removeSettings(settings) { Object.keys(settings).forEach(function(name) { var setting = settings[name]; if (!('name' in setting)) setting.name = name; this.removeSettings(setting); }, this); }, getSettingNames: function() { return this._settingNames; }, getSetting: function(name) { return this._settings[name]; }, /** * A Persister is able to store settings. It is an object that defines * two functions: * loadInitialValues(settings) and persistValue(settings, key, value). */ setPersister: function(persister) { this._persister = persister; if (persister) { persister.loadInitialValues(this); } }, resetAll: function() { this.getSettingNames().forEach(function(key) { this.resetValue(key); }, this); }, /** * Retrieve a list of the known settings and their values */ _list: function() { var reply = []; this.getSettingNames().forEach(function(setting) { reply.push({ 'key': setting, 'value': this.getSetting(setting).get() }); }, this); return reply; }, /** * Prime the local cache with the defaults. */ _loadDefaultValues: function() { this._loadFromObject(this._getDefaultValues()); }, /** * Utility to load settings from an object */ _loadFromObject: function(data) { // We iterate over data rather than keys so we don't forget values // which don't have a setting yet. for (var key in data) { if (data.hasOwnProperty(key)) { var setting = this._settings[key]; if (setting) { var value = setting.type.parse(data[key]); this.set(key, value); } else { this.set(key, data[key]); } } } }, /** * Utility to grab all the settings and export them into an object */ _saveToObject: function() { return this.getSettingNames().map(function(key) { return this._settings[key].type.stringify(this.get(key)); }.bind(this)); }, /** * The default initial settings */ _getDefaultValues: function() { return this.getSettingNames().map(function(key) { return this._settings[key].spec.defaultValue; }.bind(this)); } }; exports.settings = new Settings(); /** * Save the settings in a cookie * This code has not been tested since reboot * @constructor */ function CookiePersister() { }; CookiePersister.prototype = { loadInitialValues: function(settings) { settings._loadDefaultValues(); var data = cookie.get('settings'); settings._loadFromObject(JSON.parse(data)); }, persistValue: function(settings, key, value) { try { var stringData = JSON.stringify(settings._saveToObject()); cookie.set('settings', stringData); } catch (ex) { console.error('Unable to JSONify the settings! ' + ex); return; } } }; exports.CookiePersister = CookiePersister; }); /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Skywriter. * * The Initial Developer of the Original Code is * Mozilla. * Portions created by the Initial Developer are Copyright (C) 2009 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Skywriter Team (skywriter@mozilla.com) * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('pilot/commands/settings', function(require, exports, module) { var setCommandSpec = { name: 'set', params: [ { name: 'setting', type: 'setting', description: 'The name of the setting to display or alter', defaultValue: null }, { name: 'value', type: 'settingValue', description: 'The new value for the chosen setting', defaultValue: null } ], description: 'define and show settings', exec: function(env, args, request) { var html; if (!args.setting) { // 'set' by itself lists all the settings var names = env.settings.getSettingNames(); html = ''; // first sort the settingsList based on the name names.sort(function(name1, name2) { return name1.localeCompare(name2); }); names.forEach(function(name) { var setting = env.settings.getSetting(name); var url = 'https://wiki.mozilla.org/Labs/Skywriter/Settings#' + setting.name; html += '' + setting.name + ' = ' + setting.value + '
'; }); } else { // set with only a setting, shows the value for that setting if (args.value === undefined) { html = '' + setting.name + ' = ' + setting.get(); } else { // Actually change the setting args.setting.set(args.value); html = 'Setting: ' + args.setting.name + ' = ' + args.setting.get(); } } request.done(html); } }; var unsetCommandSpec = { name: 'unset', params: [ { name: 'setting', type: 'setting', description: 'The name of the setting to return to defaults' } ], description: 'unset a setting entirely', exec: function(env, args, request) { var setting = env.settings.get(args.setting); if (!setting) { request.doneWithError('No setting with the name ' + args.setting + '.'); return; } setting.reset(); request.done('Reset ' + setting.name + ' to default: ' + env.settings.get(args.setting)); } }; var canon = require('pilot/canon'); exports.startup = function(data, reason) { canon.addCommand(setCommandSpec); canon.addCommand(unsetCommandSpec); }; exports.shutdown = function(data, reason) { canon.removeCommand(setCommandSpec); canon.removeCommand(unsetCommandSpec); }; }); /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Skywriter. * * The Initial Developer of the Original Code is * Mozilla. * Portions created by the Initial Developer are Copyright (C) 2009 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Skywriter Team (skywriter@mozilla.com) * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('pilot/commands/basic', function(require, exports, module) { var checks = require("pilot/typecheck"); var canon = require('pilot/canon'); /** * */ var helpMessages = { plainPrefix: '

Welcome to Skywriter - Code in the Cloud

', plainSuffix: 'For more information, see the Skywriter Wiki.' }; /** * 'help' command */ var helpCommandSpec = { name: 'help', params: [ { name: 'search', type: 'text', description: 'Search string to narrow the output.', defaultValue: null } ], description: 'Get help on the available commands.', exec: function(env, args, request) { var output = []; var command = canon.getCommand(args.search); if (command && command.exec) { // caught a real command output.push(command.description ? command.description : 'No description for ' + args.search); } else { var showHidden = false; if (!args.search && helpMessages.plainPrefix) { output.push(helpMessages.plainPrefix); } if (command) { // We must be looking at sub-commands output.push('

Sub-Commands of ' + command.name + '

'); output.push('

' + command.description + '

'); } else if (args.search) { if (args.search == 'hidden') { // sneaky, sneaky. args.search = ''; showHidden = true; } output.push('

Commands starting with \'' + args.search + '\':

'); } else { output.push('

Available Commands:

'); } var commandNames = canon.getCommandNames(); commandNames.sort(); output.push(''); for (var i = 0; i < commandNames.length; i++) { command = canon.getCommand(commandNames[i]); if (!showHidden && command.hidden) { continue; } if (command.description === undefined) { // Ignore editor actions continue; } if (args.search && command.name.indexOf(args.search) !== 0) { // Filtered out by the user continue; } if (!args.search && command.name.indexOf(' ') != -1) { // sub command continue; } if (command && command.name == args.search) { // sub command, and we've already given that help continue; } // todo add back a column with parameter information, perhaps? output.push(''); output.push(''); output.push(''); output.push(''); } output.push('
' + command.name + '' + command.description + '
'); if (!args.search && helpMessages.plainSuffix) { output.push(helpMessages.plainSuffix); } } request.done(output.join('')); } }; /** * 'eval' command */ var evalCommandSpec = { name: 'eval', params: [ { name: 'javascript', type: 'text', description: 'The JavaScript to evaluate' } ], description: 'evals given js code and show the result', hidden: true, exec: function(env, args, request) { var result; var javascript = args.javascript; try { result = eval(javascript); } catch (e) { result = 'Error: ' + e.message + ''; } var msg = ''; var type = ''; var x; if (checks.isFunction(result)) { // converts the function to a well formated string msg = (result + '').replace(/\n/g, '
').replace(/ /g, ' '); type = 'function'; } else if (checks.isObject(result)) { if (Array.isArray(result)) { type = 'array'; } else { type = 'object'; } var items = []; var value; for (x in result) { if (result.hasOwnProperty(x)) { if (checks.isFunction(result[x])) { value = '[function]'; } else if (checks.isObject(result[x])) { value = '[object]'; } else { value = result[x]; } items.push({name: x, value: value}); } } items.sort(function(a,b) { return (a.name.toLowerCase() < b.name.toLowerCase()) ? -1 : 1; }); for (x = 0; x < items.length; x++) { msg += '' + items[x].name + ': ' + items[x].value + '
'; } } else { msg = result; type = typeof result; } request.done('Result for eval \'' + javascript + '\'' + ' (type: '+ type+'):

'+ msg); } }; /** * 'version' command */ var versionCommandSpec = { name: 'version', description: 'show the Skywriter version', hidden: true, exec: function(env, args, request) { var version = 'Skywriter ' + skywriter.versionNumber + ' (' + skywriter.versionCodename + ')'; request.done(version); } }; /** * 'skywriter' command */ var skywriterCommandSpec = { name: 'skywriter', hidden: true, exec: function(env, args, request) { var index = Math.floor(Math.random() * messages.length); request.done('Skywriter ' + messages[index]); } }; var messages = [ 'really wants you to trick it out in some way.', 'is your Web editor.', 'would love to be like Emacs on the Web.', 'is written on the Web platform, so you can tweak it.' ]; var canon = require('pilot/canon'); exports.startup = function(data, reason) { canon.addCommand(helpCommandSpec); canon.addCommand(evalCommandSpec); // canon.addCommand(versionCommandSpec); canon.addCommand(skywriterCommandSpec); }; exports.shutdown = function(data, reason) { canon.removeCommand(helpCommandSpec); canon.removeCommand(evalCommandSpec); // canon.removeCommand(versionCommandSpec); canon.removeCommand(skywriterCommandSpec); }; }); /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Mozilla Skywriter. * * The Initial Developer of the Original Code is * Mozilla. * Portions created by the Initial Developer are Copyright (C) 2009 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Joe Walker (jwalker@mozilla.com) * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('pilot/settings/canon', function(require, exports, module) { var historyLengthSetting = { name: "historyLength", description: "How many typed commands do we recall for reference?", type: "number", defaultValue: 50 }; exports.startup = function(data, reason) { data.env.settings.addSetting(historyLengthSetting); }; exports.shutdown = function(data, reason) { data.env.settings.removeSetting(historyLengthSetting); }; }); /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Skywriter. * * The Initial Developer of the Original Code is * Mozilla. * Portions created by the Initial Developer are Copyright (C) 2009 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Kevin Dangoor (kdangoor@mozilla.com) * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('pilot/plugin_manager', function(require, exports, module) { var Promise = require("pilot/promise").Promise; exports.REASONS = { APP_STARTUP: 1, APP_SHUTDOWN: 2, PLUGIN_ENABLE: 3, PLUGIN_DISABLE: 4, PLUGIN_INSTALL: 5, PLUGIN_UNINSTALL: 6, PLUGIN_UPGRADE: 7, PLUGIN_DOWNGRADE: 8 }; exports.Plugin = function(name) { this.name = name; this.status = this.INSTALLED; }; exports.Plugin.prototype = { /** * constants for the state */ NEW: 0, INSTALLED: 1, REGISTERED: 2, STARTED: 3, UNREGISTERED: 4, SHUTDOWN: 5, install: function(data, reason) { var pr = new Promise(); if (this.status > this.NEW) { pr.resolve(this); return pr; } require([this.name], function(pluginModule) { if (pluginModule.install) { pluginModule.install(data, reason); } this.status = this.INSTALLED; pr.resolve(this); }.bind(this)); return pr; }, register: function(data, reason) { var pr = new Promise(); if (this.status != this.INSTALLED) { pr.resolve(this); return pr; } require([this.name], function(pluginModule) { if (pluginModule.register) { pluginModule.register(data, reason); } this.status = this.REGISTERED; pr.resolve(this); }.bind(this)); return pr; }, startup: function(data, reason) { reason = reason || exports.REASONS.APP_STARTUP; var pr = new Promise(); if (this.status != this.REGISTERED) { pr.resolve(this); return pr; } require([this.name], function(pluginModule) { if (pluginModule.startup) { pluginModule.startup(data, reason); } this.status = this.STARTED; pr.resolve(this); }.bind(this)); return pr; }, shutdown: function(data, reason) { if (this.status != this.STARTED) { return; } pluginModule = require(this.name); if (pluginModule.shutdown) { pluginModule.shutdown(data, reason); } } }; exports.PluginCatalog = function() { this.plugins = {}; }; exports.PluginCatalog.prototype = { registerPlugins: function(pluginList, data, reason) { var registrationPromises = []; pluginList.forEach(function(pluginName) { var plugin = this.plugins[pluginName]; if (plugin === undefined) { plugin = new exports.Plugin(pluginName); this.plugins[pluginName] = plugin; registrationPromises.push(plugin.register(data, reason)); } }.bind(this)); return Promise.group(registrationPromises); }, startupPlugins: function(data, reason) { var startupPromises = []; for (var pluginName in this.plugins) { var plugin = this.plugins[pluginName]; startupPromises.push(plugin.startup(data, reason)); } return Promise.group(startupPromises); } }; exports.catalog = new exports.PluginCatalog(); }); /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Mozilla Skywriter. * * The Initial Developer of the Original Code is * Mozilla. * Portions created by the Initial Developer are Copyright (C) 2009 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Joe Walker (jwalker@mozilla.com) * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('pilot/promise', function(require, exports, module) { var console = require("pilot/console"); var Trace = require('pilot/stacktrace').Trace; /** * A promise can be in one of 2 states. * The ERROR and SUCCESS states are terminal, the PENDING state is the only * start state. */ var ERROR = -1; var PENDING = 0; var SUCCESS = 1; /** * We give promises and ID so we can track which are outstanding */ var _nextId = 0; /** * Debugging help if 2 things try to complete the same promise. * This can be slow (especially on chrome due to the stack trace unwinding) so * we should leave this turned off in normal use. */ var _traceCompletion = false; /** * Outstanding promises. Handy list for debugging only. */ var _outstanding = []; /** * Recently resolved promises. Also for debugging only. */ var _recent = []; /** * Create an unfulfilled promise */ Promise = function () { this._status = PENDING; this._value = undefined; this._onSuccessHandlers = []; this._onErrorHandlers = []; // Debugging help this._id = _nextId++; //this._createTrace = new Trace(new Error()); _outstanding[this._id] = this; }; /** * Yeay for RTTI. */ Promise.prototype.isPromise = true; /** * Have we either been resolve()ed or reject()ed? */ Promise.prototype.isComplete = function() { return this._status != PENDING; }; /** * Have we resolve()ed? */ Promise.prototype.isResolved = function() { return this._status == SUCCESS; }; /** * Have we reject()ed? */ Promise.prototype.isRejected = function() { return this._status == ERROR; }; /** * Take the specified action of fulfillment of a promise, and (optionally) * a different action on promise rejection. */ Promise.prototype.then = function(onSuccess, onError) { if (typeof onSuccess === 'function') { if (this._status === SUCCESS) { onSuccess.call(null, this._value); } else if (this._status === PENDING) { this._onSuccessHandlers.push(onSuccess); } } if (typeof onError === 'function') { if (this._status === ERROR) { onError.call(null, this._value); } else if (this._status === PENDING) { this._onErrorHandlers.push(onError); } } return this; }; /** * Like then() except that rather than returning this we return * a promise which */ Promise.prototype.chainPromise = function(onSuccess) { var chain = new Promise(); chain._chainedFrom = this; this.then(function(data) { try { chain.resolve(onSuccess(data)); } catch (ex) { chain.reject(ex); } }, function(ex) { chain.reject(ex); }); return chain; }; /** * Supply the fulfillment of a promise */ Promise.prototype.resolve = function(data) { return this._complete(this._onSuccessHandlers, SUCCESS, data, 'resolve'); }; /** * Renege on a promise */ Promise.prototype.reject = function(data) { return this._complete(this._onErrorHandlers, ERROR, data, 'reject'); }; /** * Internal method to be called on resolve() or reject(). * @private */ Promise.prototype._complete = function(list, status, data, name) { // Complain if we've already been completed if (this._status != PENDING) { console.group('Promise already closed'); console.error('Attempted ' + name + '() with ', data); console.error('Previous status = ', this._status, ', previous value = ', this._value); console.trace(); if (this._completeTrace) { console.error('Trace of previous completion:'); this._completeTrace.log(5); } console.groupEnd(); return this; } if (_traceCompletion) { this._completeTrace = new Trace(new Error()); } this._status = status; this._value = data; // Call all the handlers, and then delete them list.forEach(function(handler) { handler.call(null, this._value); }, this); this._onSuccessHandlers.length = 0; this._onErrorHandlers.length = 0; // Remove the given {promise} from the _outstanding list, and add it to the // _recent list, pruning more than 20 recent promises from that list. delete _outstanding[this._id]; _recent.push(this); while (_recent.length > 20) { _recent.shift(); } return this; }; /** * Takes an array of promises and returns a promise that that is fulfilled once * all the promises in the array are fulfilled * @param group The array of promises * @return the promise that is fulfilled when all the array is fulfilled */ Promise.group = function(promiseList) { if (!(promiseList instanceof Array)) { promiseList = Array.prototype.slice.call(arguments); } // If the original array has nothing in it, return now to avoid waiting if (promiseList.length === 0) { return new Promise().resolve([]); } var groupPromise = new Promise(); var results = []; var fulfilled = 0; var onSuccessFactory = function(index) { return function(data) { results[index] = data; fulfilled++; // If the group has already failed, silently drop extra results if (groupPromise._status !== ERROR) { if (fulfilled === promiseList.length) { groupPromise.resolve(results); } } }; }; promiseList.forEach(function(promise, index) { var onSuccess = onSuccessFactory(index); var onError = groupPromise.reject.bind(groupPromise); promise.then(onSuccess, onError); }); return groupPromise; }; exports.Promise = Promise; exports._outstanding = _outstanding; exports._recent = _recent; }); /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is DomTemplate. * * The Initial Developer of the Original Code is Mozilla. * Portions created by the Initial Developer are Copyright (C) 2009 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Joe Walker (jwalker@mozilla.com) (original author) * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('pilot/environment', function(require, exports, module) { var settings = require("pilot/settings").settings; /** * Create an environment object */ function create() { return { settings: settings }; }; exports.create = create; }); /* vim:ts=4:sts=4:sw=4: * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Ajax.org Code Editor (ACE). * * The Initial Developer of the Original Code is * Ajax.org B.V. * Portions created by the Initial Developer are Copyright (C) 2010 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Fabian Jakobs * Irakli Gozalishvili (http://jeditoolkit.com) * Julian Viereck * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('ace/editor', function(require, exports, module) { require("pilot/fixoldbrowsers"); var oop = require("pilot/oop"); var event = require("pilot/event"); var lang = require("pilot/lang"); var useragent = require("pilot/useragent"); var TextInput = require("ace/keyboard/textinput").TextInput; var MouseHandler = require("ace/mouse_handler").MouseHandler; //var TouchHandler = require("ace/touch_handler").TouchHandler; var KeyBinding = require("ace/keyboard/keybinding").KeyBinding; var EditSession = require("ace/edit_session").EditSession; var Search = require("ace/search").Search; var BackgroundTokenizer = require("ace/background_tokenizer").BackgroundTokenizer; var Range = require("ace/range").Range; var EventEmitter = require("pilot/event_emitter").EventEmitter; var Editor =function(renderer, session) { var container = renderer.getContainerElement(); this.container = container; this.renderer = renderer; this.textInput = new TextInput(renderer.getTextAreaContainer(), this); this.keyBinding = new KeyBinding(this); // TODO detect touch event support if (useragent.isIPad) { //this.$mouseHandler = new TouchHandler(this); } else { this.$mouseHandler = new MouseHandler(this); } this.$blockScrolling = 0; this.$search = new Search().set({ wrap: true }); this.setSession(session || new EditSession("")); }; (function(){ oop.implement(this, EventEmitter); this.$forwardEvents = { gutterclick: 1, gutterdblclick: 1 }; this.$originalAddEventListener = this.addEventListener; this.$originalRemoveEventListener = this.removeEventListener; this.addEventListener = function(eventName, callback) { if (this.$forwardEvents[eventName]) { return this.renderer.addEventListener(eventName, callback); } else { return this.$originalAddEventListener(eventName, callback); } }; this.removeEventListener = function(eventName, callback) { if (this.$forwardEvents[eventName]) { return this.renderer.removeEventListener(eventName, callback); } else { return this.$originalRemoveEventListener(eventName, callback); } }; this.setKeyboardHandler = function(keyboardHandler) { this.keyBinding.setKeyboardHandler(keyboardHandler); }; this.getKeyboardHandler = function() { return this.keyBinding.getKeyboardHandler(); }; this.setSession = function(session) { if (this.session == session) return; if (this.session) { var oldSession = this.session; this.session.removeEventListener("change", this.$onDocumentChange); this.session.removeEventListener("changeMode", this.$onChangeMode); this.session.removeEventListener("changeTabSize", this.$onChangeTabSize); this.session.removeEventListener("changeWrapLimit", this.$onChangeWrapLimit); this.session.removeEventListener("changeWrapMode", this.$onChangeWrapMode); this.session.removeEventListener("changeFrontMarker", this.$onChangeFrontMarker); this.session.removeEventListener("changeBackMarker", this.$onChangeBackMarker); this.session.removeEventListener("changeBreakpoint", this.$onChangeBreakpoint); this.session.removeEventListener("changeAnnotation", this.$onChangeAnnotation); this.session.removeEventListener("changeOverwrite", this.$onCursorChange); var selection = this.session.getSelection(); selection.removeEventListener("changeCursor", this.$onCursorChange); selection.removeEventListener("changeSelection", this.$onSelectionChange); this.session.setScrollTopRow(this.renderer.getScrollTopRow()); } this.session = session; this.$onDocumentChange = this.onDocumentChange.bind(this); session.addEventListener("change", this.$onDocumentChange); this.renderer.setSession(session); this.$onChangeMode = this.onChangeMode.bind(this); session.addEventListener("changeMode", this.$onChangeMode); this.$onChangeTabSize = this.renderer.updateText.bind(this.renderer); session.addEventListener("changeTabSize", this.$onChangeTabSize); this.$onChangeWrapLimit = this.onChangeWrapLimit.bind(this); session.addEventListener("changeWrapLimit", this.$onChangeWrapLimit); this.$onChangeWrapMode = this.onChangeWrapMode.bind(this); session.addEventListener("changeWrapMode", this.$onChangeWrapMode); this.$onChangeFrontMarker = this.onChangeFrontMarker.bind(this); this.session.addEventListener("changeFrontMarker", this.$onChangeFrontMarker); this.$onChangeBackMarker = this.onChangeBackMarker.bind(this); this.session.addEventListener("changeBackMarker", this.$onChangeBackMarker); this.$onChangeBreakpoint = this.onChangeBreakpoint.bind(this); this.session.addEventListener("changeBreakpoint", this.$onChangeBreakpoint); this.$onChangeAnnotation = this.onChangeAnnotation.bind(this); this.session.addEventListener("changeAnnotation", this.$onChangeAnnotation); this.$onCursorChange = this.onCursorChange.bind(this); this.session.addEventListener("changeOverwrite", this.$onCursorChange); this.selection = session.getSelection(); this.selection.addEventListener("changeCursor", this.$onCursorChange); this.$onSelectionChange = this.onSelectionChange.bind(this); this.selection.addEventListener("changeSelection", this.$onSelectionChange); this.onChangeMode(); this.bgTokenizer.setDocument(session.getDocument()); this.bgTokenizer.start(0); this.onCursorChange(); this.onSelectionChange(); this.onChangeFrontMarker(); this.onChangeBackMarker(); this.onChangeBreakpoint(); this.onChangeAnnotation(); this.renderer.scrollToRow(session.getScrollTopRow()); this.renderer.updateFull(); this._dispatchEvent("changeSession", { session: session, oldSession: oldSession }); }; this.getSession = function() { return this.session; }; this.getSelection = function() { return this.selection; }; this.resize = function() { this.renderer.onResize(); }; this.setTheme = function(theme) { this.renderer.setTheme(theme); }; this.setStyle = function(style) { this.renderer.setStyle(style) }; this.unsetStyle = function(style) { this.renderer.unsetStyle(style) } this.$highlightBrackets = function() { if (this.session.$bracketHighlight) { this.session.removeMarker(this.session.$bracketHighlight); this.session.$bracketHighlight = null; } if (this.$highlightPending) { return; } // perform highlight async to not block the browser during navigation var self = this; this.$highlightPending = true; setTimeout(function() { self.$highlightPending = false; var pos = self.session.findMatchingBracket(self.getCursorPosition()); if (pos) { var range = new Range(pos.row, pos.column, pos.row, pos.column+1); self.session.$bracketHighlight = self.session.addMarker(range, "ace_bracket"); } }, 10); }; this.focus = function() { // Safari needs the timeout // iOS and Firefox need it called immediately // to be on the save side we do both // except for IE var _self = this; if (!useragent.isIE) { setTimeout(function() { _self.textInput.focus(); }); } this.textInput.focus(); }; this.blur = function() { this.textInput.blur(); }; this.onFocus = function() { this.renderer.showCursor(); this.renderer.visualizeFocus(); this._dispatchEvent("focus"); }; this.onBlur = function() { this.renderer.hideCursor(); this.renderer.visualizeBlur(); this._dispatchEvent("blur"); }; this.onDocumentChange = function(e) { var delta = e.data; var range = delta.range; this.bgTokenizer.start(range.start.row); if (range.start.row == range.end.row && delta.action != "insertLines" && delta.action != "removeLines") var lastRow = range.end.row; else lastRow = Infinity; this.renderer.updateLines(range.start.row, lastRow); // update cursor because tab characters can influence the cursor position this.renderer.updateCursor(); }; this.onTokenizerUpdate = function(e) { var rows = e.data; this.renderer.updateLines(rows.first, rows.last); }; this.onCursorChange = function(e) { this.renderer.updateCursor(); if (!this.$blockScrolling) { this.renderer.scrollCursorIntoView(); } // move text input over the cursor // this is required for iOS and IME this.renderer.moveTextAreaToCursor(this.textInput.getElement()); this.$highlightBrackets(); this.$updateHighlightActiveLine(); }; this.$updateHighlightActiveLine = function() { var session = this.getSession(); if (session.$highlightLineMarker) { session.removeMarker(session.$highlightLineMarker); } session.$highlightLineMarker = null; if (this.getHighlightActiveLine() && (this.getSelectionStyle() != "line" || !this.selection.isMultiLine())) { var cursor = this.getCursorPosition(); var range = new Range(cursor.row, 0, cursor.row+1, 0); session.$highlightLineMarker = session.addMarker(range, "ace_active_line", "line"); } }; this.onSelectionChange = function(e) { var session = this.getSession(); if (session.$selectionMarker) { session.removeMarker(session.$selectionMarker); } session.$selectionMarker = null; if (!this.selection.isEmpty()) { var range = this.selection.getRange(); var style = this.getSelectionStyle(); session.$selectionMarker = session.addMarker(range, "ace_selection", style); } this.onCursorChange(e); if (this.$highlightSelectedWord) this.mode.highlightSelection(this); }; this.onChangeFrontMarker = function() { this.renderer.updateFrontMarkers(); }; this.onChangeBackMarker = function() { this.renderer.updateBackMarkers(); }; this.onChangeBreakpoint = function() { this.renderer.setBreakpoints(this.session.getBreakpoints()); }; this.onChangeAnnotation = function() { this.renderer.setAnnotations(this.session.getAnnotations()); }; this.onChangeMode = function() { var mode = this.session.getMode(); if (this.mode == mode) return; this.mode = mode; var tokenizer = mode.getTokenizer(); if (!this.bgTokenizer) { var onUpdate = this.onTokenizerUpdate.bind(this); this.bgTokenizer = new BackgroundTokenizer(tokenizer, this); this.bgTokenizer.addEventListener("update", onUpdate); } else { this.bgTokenizer.setTokenizer(tokenizer); } this.renderer.setTokenizer(this.bgTokenizer); }; this.onChangeWrapLimit = function() { this.renderer.updateFull(); }; this.onChangeWrapMode = function() { this.renderer.onResize(true); }; this.getCopyText = function() { if (!this.selection.isEmpty()) { return this.session.getTextRange(this.getSelectionRange()); } else { return ""; } }; this.onCut = function() { if (this.$readOnly) return; if (!this.selection.isEmpty()) { this.session.remove(this.getSelectionRange()) this.clearSelection(); } }; this.insert = function(text) { if (this.$readOnly) return; var cursor = this.getCursorPosition(); text = text.replace("\t", this.session.getTabString()); // remove selected text if (!this.selection.isEmpty()) { var cursor = this.session.remove(this.getSelectionRange()); this.clearSelection(); } else if (this.session.getOverwrite()) { var range = new Range.fromPoints(cursor, cursor); range.end.column += text.length; this.session.remove(range); } this.clearSelection(); var lineState = this.bgTokenizer.getState(cursor.row); var shouldOutdent = this.mode.checkOutdent(lineState, this.session.getLine(cursor.row), text); var line = this.session.getLine(cursor.row); var lineIndent = this.mode.getNextLineIndent(lineState, line.slice(0, cursor.column), this.session.getTabString()); var end = this.session.insert(cursor, text); var lineState = this.bgTokenizer.getState(cursor.row); // TODO disabled multiline auto indent // possibly doing the indent before inserting the text // if (cursor.row !== end.row) { if (this.session.getDocument().isNewLine(text)) { this.moveCursorTo(cursor.row+1, 0); var size = this.session.getTabSize(), minIndent = Number.MAX_VALUE; for (var row = cursor.row + 1; row <= end.row; ++row) { var indent = 0; line = this.session.getLine(row); for (var i = 0; i < line.length; ++i) if (line.charAt(i) == '\t') indent += size; else if (line.charAt(i) == ' ') indent += 1; else break; if (/[^\s]/.test(line)) minIndent = Math.min(indent, minIndent); } for (var row = cursor.row + 1; row <= end.row; ++row) { var outdent = minIndent; line = this.session.getLine(row); for (var i = 0; i < line.length && outdent > 0; ++i) if (line.charAt(i) == '\t') outdent -= size; else if (line.charAt(i) == ' ') outdent -= 1; this.session.remove(new Range(row, 0, row, i)); } this.session.indentRows(cursor.row + 1, end.row, lineIndent); } else { if (shouldOutdent) { this.mode.autoOutdent(lineState, this.session, cursor.row); } }; } this.onTextInput = function(text) { this.keyBinding.onTextInput(text); }; this.onCommandKey = function(e, hashId, keyCode) { this.keyBinding.onCommandKey(e, hashId, keyCode); }; this.setOverwrite = function(overwrite) { this.session.setOverwrite(); }; this.getOverwrite = function() { return this.session.getOverwrite(); }; this.toggleOverwrite = function() { this.session.toggleOverwrite(); }; this.setScrollSpeed = function(speed) { this.$mouseHandler.setScrollSpeed(speed); }; this.getScrollSpeed = function() { return this.$mouseHandler.getScrollSpeed() }; this.$selectionStyle = "line"; this.setSelectionStyle = function(style) { if (this.$selectionStyle == style) return; this.$selectionStyle = style; this.onSelectionChange(); this._dispatchEvent("changeSelectionStyle", {data: style}); }; this.getSelectionStyle = function() { return this.$selectionStyle; }; this.$highlightActiveLine = true; this.setHighlightActiveLine = function(shouldHighlight) { if (this.$highlightActiveLine == shouldHighlight) return; this.$highlightActiveLine = shouldHighlight; this.$updateHighlightActiveLine(); }; this.getHighlightActiveLine = function() { return this.$highlightActiveLine; }; this.$highlightSelectedWord = true; this.setHighlightSelectedWord = function(shouldHighlight) { if (this.$highlightSelectedWord == shouldHighlight) return; this.$highlightSelectedWord = shouldHighlight; if (shouldHighlight) this.mode.highlightSelection(this); else this.mode.clearSelectionHighlight(this); }; this.getHighlightSelectedWord = function() { return this.$highlightSelectedWord; }; this.setShowInvisibles = function(showInvisibles) { if (this.getShowInvisibles() == showInvisibles) return; this.renderer.setShowInvisibles(showInvisibles); }; this.getShowInvisibles = function() { return this.renderer.getShowInvisibles(); }; this.setShowPrintMargin = function(showPrintMargin) { this.renderer.setShowPrintMargin(showPrintMargin); }; this.getShowPrintMargin = function() { return this.renderer.getShowPrintMargin(); }; this.setPrintMarginColumn = function(showPrintMargin) { this.renderer.setPrintMarginColumn(showPrintMargin); }; this.getPrintMarginColumn = function() { return this.renderer.getPrintMarginColumn(); }; this.$readOnly = false; this.setReadOnly = function(readOnly) { this.$readOnly = readOnly; }; this.getReadOnly = function() { return this.$readOnly; }; this.removeRight = function() { if (this.$readOnly) return; if (this.selection.isEmpty()) { this.selection.selectRight(); } this.session.remove(this.getSelectionRange()) this.clearSelection(); }; this.removeLeft = function() { if (this.$readOnly) return; if (this.selection.isEmpty()) this.selection.selectLeft(); this.session.remove(this.getSelectionRange()); this.clearSelection(); }; this.removeWordRight = function() { if (this.$readOnly) return; if (this.selection.isEmpty()) this.selection.selectWordRight(); this.session.remove(this.getSelectionRange()); this.clearSelection(); }; this.removeWordLeft = function() { if (this.$readOnly) return; if (this.selection.isEmpty()) this.selection.selectWordLeft(); this.session.remove(this.getSelectionRange()); this.clearSelection(); }; this.removeToLineStart = function() { if (this.$readOnly) return; if (this.selection.isEmpty()) this.selection.selectLineStart(); this.session.remove(this.getSelectionRange()); this.clearSelection(); }; this.removeToLineEnd = function() { if (this.$readOnly) return; if (this.selection.isEmpty()) this.selection.selectLineEnd(); this.session.remove(this.getSelectionRange()); this.clearSelection(); }; this.splitLine = function() { if (this.$readOnly) return; if (!this.selection.isEmpty()) { this.session.remove(this.getSelectionRange()); this.clearSelection(); } var cursor = this.getCursorPosition(); this.insert("\n"); this.moveCursorToPosition(cursor); }; this.transposeLetters = function() { if (this.$readOnly) return; if (!this.selection.isEmpty()) { return; } var cursor = this.getCursorPosition(); var column = cursor.column; if (column == 0) return; var line = this.session.getLine(cursor.row); if (column < line.length) { var swap = line.charAt(column) + line.charAt(column-1); var range = new Range(cursor.row, column-1, cursor.row, column+1) } else { var swap = line.charAt(column-1) + line.charAt(column-2); var range = new Range(cursor.row, column-2, cursor.row, column) } this.session.replace(range, swap); }; this.indent = function() { if (this.$readOnly) return; var session = this.session; var range = this.getSelectionRange(); if (range.start.row < range.end.row || range.start.column < range.end.column) { var rows = this.$getSelectedRows(); session.indentRows(rows.first, rows.last, "\t"); } else { var indentString; if (this.session.getUseSoftTabs()) { var size = session.getTabSize(), position = this.getCursorPosition(), column = session.documentToScreenColumn(position.row, position.column), count = (size - column % size); indentString = lang.stringRepeat(" ", count); } else indentString = "\t"; return this.onTextInput(indentString); } }; this.blockOutdent = function() { if (this.$readOnly) return; var selection = this.session.getSelection(); this.session.outdentRows(selection.getRange()); }; this.toggleCommentLines = function() { if (this.$readOnly) return; var state = this.bgTokenizer.getState(this.getCursorPosition().row); var rows = this.$getSelectedRows() this.mode.toggleCommentLines(state, this.session, rows.first, rows.last); }; this.removeLines = function() { if (this.$readOnly) return; var rows = this.$getSelectedRows(); this.session.remove(new Range(rows.first, 0, rows.last+1, 0)); this.clearSelection(); }; this.moveLinesDown = function() { if (this.$readOnly) return; this.$moveLines(function(firstRow, lastRow) { return this.session.moveLinesDown(firstRow, lastRow); }); }; this.moveLinesUp = function() { if (this.$readOnly) return; this.$moveLines(function(firstRow, lastRow) { return this.session.moveLinesUp(firstRow, lastRow); }); }; this.moveText = function(range, toPosition) { if (this.$readOnly) return null; return this.session.moveText(range, toPosition); }; this.copyLinesUp = function() { if (this.$readOnly) return; this.$moveLines(function(firstRow, lastRow) { this.session.duplicateLines(firstRow, lastRow); return 0; }); }; this.copyLinesDown = function() { if (this.$readOnly) return; this.$moveLines(function(firstRow, lastRow) { return this.session.duplicateLines(firstRow, lastRow); }); }; this.$moveLines = function(mover) { var rows = this.$getSelectedRows(); var linesMoved = mover.call(this, rows.first, rows.last); var selection = this.selection; selection.setSelectionAnchor(rows.last+linesMoved+1, 0); selection.$moveSelection(function() { selection.moveCursorTo(rows.first+linesMoved, 0); }); }; this.$getSelectedRows = function() { var range = this.getSelectionRange().collapseRows(); return { first: range.start.row, last: range.end.row }; }; this.onCompositionStart = function(text) { this.renderer.showComposition(this.getCursorPosition()); }; this.onCompositionUpdate = function(text) { this.renderer.setCompositionText(text); }; this.onCompositionEnd = function() { this.renderer.hideComposition(); }; this.getFirstVisibleRow = function() { return this.renderer.getFirstVisibleRow(); }; this.getLastVisibleRow = function() { return this.renderer.getLastVisibleRow(); }; this.isRowVisible = function(row) { return (row >= this.getFirstVisibleRow() && row <= this.getLastVisibleRow()); }; this.$getVisibleRowCount = function() { return this.renderer.getScrollBottomRow() - this.renderer.getScrollTopRow() + 1; }; this.$getPageDownRow = function() { return this.renderer.getScrollBottomRow(); }; this.$getPageUpRow = function() { var firstRow = this.renderer.getScrollTopRow(); var lastRow = this.renderer.getScrollBottomRow(); return firstRow - (lastRow - firstRow); }; this.selectPageDown = function() { var row = this.$getPageDownRow() + Math.floor(this.$getVisibleRowCount() / 2); this.scrollPageDown(); var selection = this.getSelection(); var leadScreenPos = this.session.documentToScreenPosition(selection.getSelectionLead()); var dest = this.session.screenToDocumentPosition(row, leadScreenPos.column); selection.selectTo(dest.row, dest.column); }; this.selectPageUp = function() { var visibleRows = this.renderer.getScrollTopRow() - this.renderer.getScrollBottomRow(); var row = this.$getPageUpRow() + Math.round(visibleRows / 2); this.scrollPageUp(); var selection = this.getSelection(); var leadScreenPos = this.session.documentToScreenPosition(selection.getSelectionLead()); var dest = this.session.screenToDocumentPosition(row, leadScreenPos.column); selection.selectTo(dest.row, dest.column); }; this.gotoPageDown = function() { var row = this.$getPageDownRow(); var column = this.getCursorPositionScreen().column; this.scrollToRow(row); this.getSelection().moveCursorToScreen(row, column); }; this.gotoPageUp = function() { var row = this.$getPageUpRow(); var column = this.getCursorPositionScreen().column; this.scrollToRow(row); this.getSelection().moveCursorToScreen(row, column); }; this.scrollPageDown = function() { this.scrollToRow(this.$getPageDownRow()); }; this.scrollPageUp = function() { this.renderer.scrollToRow(this.$getPageUpRow()); }; this.scrollToRow = function(row) { this.renderer.scrollToRow(row); }; this.scrollToLine = function(line, center) { this.renderer.scrollToLine(line, center); }; this.centerSelection = function() { var range = this.getSelectionRange(); var line = Math.floor(range.start.row + (range.end.row - range.start.row) / 2); this.renderer.scrollToLine(line, true); }; this.getCursorPosition = function() { return this.selection.getCursor(); }; this.getCursorPositionScreen = function() { return this.session.documentToScreenPosition(this.getCursorPosition()); }; this.getSelectionRange = function() { return this.selection.getRange(); }; this.selectAll = function() { this.$blockScrolling += 1; this.selection.selectAll(); this.$blockScrolling -= 1; }; this.clearSelection = function() { this.selection.clearSelection(); }; this.moveCursorTo = function(row, column) { this.selection.moveCursorTo(row, column); }; this.moveCursorToPosition = function(pos) { this.selection.moveCursorToPosition(pos); }; this.gotoLine = function(lineNumber, row) { this.selection.clearSelection(); this.$blockScrolling += 1; this.moveCursorTo(lineNumber-1, row || 0); this.$blockScrolling -= 1; if (!this.isRowVisible(this.getCursorPosition().row)) { this.scrollToLine(lineNumber, true); } }, this.navigateTo = function(row, column) { this.clearSelection(); this.moveCursorTo(row, column); }; this.navigateUp = function(times) { this.selection.clearSelection(); times = times || 1; this.selection.moveCursorBy(-times, 0); }; this.navigateDown = function(times) { this.selection.clearSelection(); times = times || 1; this.selection.moveCursorBy(times, 0); }; this.navigateLeft = function(times) { if (!this.selection.isEmpty()) { var selectionStart = this.getSelectionRange().start; this.moveCursorToPosition(selectionStart); } else { times = times || 1; while (times--) { this.selection.moveCursorLeft(); } } this.clearSelection(); }; this.navigateRight = function(times) { if (!this.selection.isEmpty()) { var selectionEnd = this.getSelectionRange().end; this.moveCursorToPosition(selectionEnd); } else { times = times || 1; while (times--) { this.selection.moveCursorRight(); } } this.clearSelection(); }; this.navigateLineStart = function() { this.selection.moveCursorLineStart(); this.clearSelection(); }; this.navigateLineEnd = function() { this.selection.moveCursorLineEnd(); this.clearSelection(); }; this.navigateFileEnd = function() { this.selection.moveCursorFileEnd(); this.clearSelection(); }; this.navigateFileStart = function() { this.selection.moveCursorFileStart(); this.clearSelection(); }; this.navigateWordRight = function() { this.selection.moveCursorWordRight(); this.clearSelection(); }; this.navigateWordLeft = function() { this.selection.moveCursorWordLeft(); this.clearSelection(); }; this.replace = function(replacement, options) { if (options) this.$search.set(options); var range = this.$search.find(this.session); this.$tryReplace(range, replacement); if (range !== null) this.selection.setSelectionRange(range); }, this.replaceAll = function(replacement, options) { if (options) { this.$search.set(options); } var ranges = this.$search.findAll(this.session); if (!ranges.length) return; var selection = this.getSelectionRange(); this.clearSelection(); this.selection.moveCursorTo(0, 0); this.$blockScrolling += 1; for (var i = ranges.length - 1; i >= 0; --i) this.$tryReplace(ranges[i], replacement); this.selection.setSelectionRange(selection); this.$blockScrolling -= 1; }, this.$tryReplace = function(range, replacement) { var input = this.session.getTextRange(range); var replacement = this.$search.replace(input, replacement); if (replacement !== null) { range.end = this.session.replace(range, replacement); return range; } else { return null; } }; this.getLastSearchOptions = function() { return this.$search.getOptions(); }; this.find = function(needle, options) { this.clearSelection(); options = options || {}; options.needle = needle; this.$search.set(options); this.$find(); }, this.findNext = function(options) { options = options || {}; if (typeof options.backwards == "undefined") options.backwards = false; this.$search.set(options); this.$find(); }; this.findPrevious = function(options) { options = options || {}; if (typeof options.backwards == "undefined") options.backwards = true; this.$search.set(options); this.$find(); }; this.$find = function(backwards) { if (!this.selection.isEmpty()) { this.$search.set({needle: this.session.getTextRange(this.getSelectionRange())}); } if (typeof backwards != "undefined") this.$search.set({backwards: backwards}); var range = this.$search.find(this.session); if (range) { this.gotoLine(range.end.row+1, range.end.column); this.selection.setSelectionRange(range); } }; this.undo = function() { this.session.getUndoManager().undo(); }; this.redo = function() { this.session.getUndoManager().redo(); }; }).call(Editor.prototype); exports.Editor = Editor; }); /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Ajax.org Code Editor (ACE). * * The Initial Developer of the Original Code is * Ajax.org B.V. * Portions created by the Initial Developer are Copyright (C) 2010 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Fabian Jakobs * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('pilot/event', function(require, exports, module) { var keys = require("pilot/keys"); var useragent = require("pilot/useragent"); var dom = require("pilot/dom"); exports.addListener = function(elem, type, callback) { if (elem.addEventListener) { return elem.addEventListener(type, callback, false); } if (elem.attachEvent) { var wrapper = function() { callback(window.event); }; callback._wrapper = wrapper; elem.attachEvent("on" + type, wrapper); } }; exports.removeListener = function(elem, type, callback) { if (elem.removeEventListener) { return elem.removeEventListener(type, callback, false); } if (elem.detachEvent) { elem.detachEvent("on" + type, callback._wrapper || callback); } }; /** * Prevents propagation and clobbers the default action of the passed event */ exports.stopEvent = function(e) { exports.stopPropagation(e); exports.preventDefault(e); return false; }; exports.stopPropagation = function(e) { if (e.stopPropagation) e.stopPropagation(); else e.cancelBubble = true; }; exports.preventDefault = function(e) { if (e.preventDefault) e.preventDefault(); else e.returnValue = false; }; exports.getDocumentX = function(e) { if (e.clientX) { return e.clientX + dom.getPageScrollLeft(); } else { return e.pageX; } }; exports.getDocumentY = function(e) { if (e.clientY) { return e.clientY + dom.getPageScrollTop(); } else { return e.pageY; } }; /** * @return {Number} 0 for left button, 1 for middle button, 2 for right button */ exports.getButton = function(e) { if (e.type == "dblclick") return 0; else if (e.type == "contextmenu") return 2; // DOM Event if (e.preventDefault) { return e.button; } // old IE else { return {1:0, 2:2, 4:1}[e.button]; } }; if (document.documentElement.setCapture) { exports.capture = function(el, eventHandler, releaseCaptureHandler) { function onMouseMove(e) { eventHandler(e); return exports.stopPropagation(e); } function onReleaseCapture(e) { eventHandler && eventHandler(e); releaseCaptureHandler && releaseCaptureHandler(); exports.removeListener(el, "mousemove", eventHandler); exports.removeListener(el, "mouseup", onReleaseCapture); exports.removeListener(el, "losecapture", onReleaseCapture); el.releaseCapture(); } exports.addListener(el, "mousemove", eventHandler); exports.addListener(el, "mouseup", onReleaseCapture); exports.addListener(el, "losecapture", onReleaseCapture); el.setCapture(); }; } else { exports.capture = function(el, eventHandler, releaseCaptureHandler) { function onMouseMove(e) { eventHandler(e); e.stopPropagation(); } function onMouseUp(e) { eventHandler && eventHandler(e); releaseCaptureHandler && releaseCaptureHandler(); document.removeEventListener("mousemove", onMouseMove, true); document.removeEventListener("mouseup", onMouseUp, true); e.stopPropagation(); } document.addEventListener("mousemove", onMouseMove, true); document.addEventListener("mouseup", onMouseUp, true); }; } exports.addMouseWheelListener = function(el, callback) { var listener = function(e) { if (e.wheelDelta !== undefined) { if (e.wheelDeltaX !== undefined) { e.wheelX = -e.wheelDeltaX / 8; e.wheelY = -e.wheelDeltaY / 8; } else { e.wheelX = 0; e.wheelY = -e.wheelDelta / 8; } } else { if (e.axis && e.axis == e.HORIZONTAL_AXIS) { e.wheelX = (e.detail || 0) * 5; e.wheelY = 0; } else { e.wheelX = 0; e.wheelY = (e.detail || 0) * 5; } } callback(e); }; exports.addListener(el, "DOMMouseScroll", listener); exports.addListener(el, "mousewheel", listener); }; exports.addMultiMouseDownListener = function(el, button, count, timeout, callback) { var clicks = 0; var startX, startY; var listener = function(e) { clicks += 1; if (clicks == 1) { startX = e.clientX; startY = e.clientY; setTimeout(function() { clicks = 0; }, timeout || 600); } if (exports.getButton(e) != button || Math.abs(e.clientX - startX) > 5 || Math.abs(e.clientY - startY) > 5) clicks = 0; if (clicks == count) { clicks = 0; callback(e); } return exports.preventDefault(e); }; exports.addListener(el, "mousedown", listener); useragent.isIE && exports.addListener(el, "dblclick", listener); }; function normalizeCommandKeys(callback, e, keyCode) { var hashId = 0; if (useragent.isOpera && useragent.isMac) { hashId = 0 | (e.metaKey ? 1 : 0) | (e.altKey ? 2 : 0) | (e.shiftKey ? 4 : 0) | (e.ctrlKey ? 8 : 0); } else { hashId = 0 | (e.ctrlKey ? 1 : 0) | (e.altKey ? 2 : 0) | (e.shiftKey ? 4 : 0) | (e.metaKey ? 8 : 0); } if (keyCode in keys.MODIFIER_KEYS) { switch (keys.MODIFIER_KEYS[keyCode]) { case "Alt": hashId = 2; break; case "Shift": hashId = 4; break case "Ctrl": hashId = 1; break; default: hashId = 8; break; } keyCode = 0; } if (hashId & 8 && (keyCode == 91 || keyCode == 93)) { keyCode = 0; } // If there is no hashID and the keyCode is not a function key, then // we don't call the callback as we don't handle a command key here // (it's a normal key/character input). if (hashId == 0 && !(keyCode in keys.FUNCTION_KEYS)) { return false; } return callback(e, hashId, keyCode); } exports.addCommandKeyListener = function(el, callback) { var addListener = exports.addListener; if (useragent.isOldGecko) { // Old versions of Gecko aka. Firefox < 4.0 didn't repeat the keydown // event if the user pressed the key for a longer time. Instead, the // keydown event was fired once and later on only the keypress event. // To emulate the 'right' keydown behavior, the keyCode of the initial // keyDown event is stored and in the following keypress events the // stores keyCode is used to emulate a keyDown event. var lastKeyDownKeyCode = null; addListener(el, "keydown", function(e) { lastKeyDownKeyCode = e.keyCode; }); addListener(el, "keypress", function(e) { return normalizeCommandKeys(callback, e, lastKeyDownKeyCode); }); } else { var lastDown = null; addListener(el, "keydown", function(e) { lastDown = e.keyIdentifier || e.keyCode; return normalizeCommandKeys(callback, e, e.keyCode); }); // repeated keys are fired as keypress and not keydown events if (useragent.isMac && useragent.isOpera) { addListener(el, "keypress", function(e) { var keyId = e.keyIdentifier || e.keyCode; if (lastDown !== keyId) { return normalizeCommandKeys(callback, e, e.keyCode); } else { lastDown = null; } }); } } }; }); /* vim:ts=4:sts=4:sw=4: * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Ajax.org Code Editor (ACE). * * The Initial Developer of the Original Code is * Ajax.org B.V. * Portions created by the Initial Developer are Copyright (C) 2010 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Fabian Jakobs * Mihai Sucan * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('pilot/dom', function(require, exports, module) { var XHTML_NS = "http://www.w3.org/1999/xhtml"; exports.createElement = function(tag, ns) { return document.createElementNS ? document.createElementNS(ns || XHTML_NS, tag) : document.createElement(tag); }; exports.setText = function(elem, text) { if (elem.innerText !== undefined) { elem.innerText = text; } if (elem.textContent !== undefined) { elem.textContent = text; } }; if (!document.documentElement.classList) { exports.hasCssClass = function(el, name) { var classes = el.className.split(/\s+/g); return classes.indexOf(name) !== -1; }; /** * Add a CSS class to the list of classes on the given node */ exports.addCssClass = function(el, name) { if (!exports.hasCssClass(el, name)) { el.className += " " + name; } }; /** * Remove a CSS class from the list of classes on the given node */ exports.removeCssClass = function(el, name) { var classes = el.className.split(/\s+/g); while (true) { var index = classes.indexOf(name); if (index == -1) { break; } classes.splice(index, 1); } el.className = classes.join(" "); }; exports.toggleCssClass = function(el, name) { var classes = el.className.split(/\s+/g), add = true; while (true) { var index = classes.indexOf(name); if (index == -1) { break; } add = false; classes.splice(index, 1); } if(add) classes.push(name); el.className = classes.join(" "); return add; }; } else { exports.hasCssClass = function(el, name) { return el.classList.contains(name); }; exports.addCssClass = function(el, name) { el.classList.add(name); }; exports.removeCssClass = function(el, name) { el.classList.remove(name); }; exports.toggleCssClass = function(el, name) { return el.classList.toggle(name); }; } /** * Add or remove a CSS class from the list of classes on the given node * depending on the value of include */ exports.setCssClass = function(node, className, include) { if (include) { exports.addCssClass(node, className); } else { exports.removeCssClass(node, className); } }; exports.importCssString = function(cssText, doc){ doc = doc || document; if (doc.createStyleSheet) { var sheet = doc.createStyleSheet(); sheet.cssText = cssText; } else { var style = doc.createElementNS ? doc.createElementNS(XHTML_NS, "style") : doc.createElement("style"); style.appendChild(doc.createTextNode(cssText)); var head = doc.getElementsByTagName("head")[0] || doc.documentElement; head.appendChild(style); } }; exports.getInnerWidth = function(element) { return (parseInt(exports.computedStyle(element, "paddingLeft")) + parseInt(exports.computedStyle(element, "paddingRight")) + element.clientWidth); }; exports.getInnerHeight = function(element) { return (parseInt(exports.computedStyle(element, "paddingTop")) + parseInt(exports.computedStyle(element, "paddingBottom")) + element.clientHeight); }; if (window.pageYOffset !== undefined) { exports.getPageScrollTop = function() { return window.pageYOffset; }; exports.getPageScrollLeft = function() { return window.pageXOffset; }; } else { exports.getPageScrollTop = function() { return document.body.scrollTop; }; exports.getPageScrollLeft = function() { return document.body.scrollLeft; }; } exports.computedStyle = function(element, style) { if (window.getComputedStyle) { return (window.getComputedStyle(element, "") || {})[style] || ""; } else { return element.currentStyle[style]; } }; exports.scrollbarWidth = function() { var inner = exports.createElement("p"); inner.style.width = "100%"; inner.style.height = "200px"; var outer = exports.createElement("div"); var style = outer.style; style.position = "absolute"; style.left = "-10000px"; style.overflow = "hidden"; style.width = "200px"; style.height = "150px"; outer.appendChild(inner); var body = document.body || document.documentElement; body.appendChild(outer); var noScrollbar = inner.offsetWidth; style.overflow = "scroll"; var withScrollbar = inner.offsetWidth; if (noScrollbar == withScrollbar) { withScrollbar = outer.clientWidth; } body.removeChild(outer); return noScrollbar-withScrollbar; }; /** * Optimized set innerHTML. This is faster than plain innerHTML if the element * already contains a lot of child elements. * * See http://blog.stevenlevithan.com/archives/faster-than-innerhtml for details */ exports.setInnerHtml = function(el, innerHtml) { var element = el.cloneNode(false);//document.createElement("div"); element.innerHTML = innerHtml; el.parentNode.replaceChild(element, el); return element; }; exports.setInnerText = function(el, innerText) { if (document.body && "textContent" in document.body) el.textContent = innerText; else el.innerText = innerText; }; exports.getInnerText = function(el) { if (document.body && "textContent" in document.body) return el.textContent; else return el.innerText || el.textContent || ""; }; exports.getParentWindow = function(document) { return document.defaultView || document.parentWindow; }; exports.getSelectionStart = function(textarea) { // TODO IE var start; try { start = textarea.selectionStart || 0; } catch (e) { start = 0; } return start; }; exports.setSelectionStart = function(textarea, start) { // TODO IE return textarea.selectionStart = start; }; exports.getSelectionEnd = function(textarea) { // TODO IE var end; try { end = textarea.selectionEnd || 0; } catch (e) { end = 0; } return end; }; exports.setSelectionEnd = function(textarea, end) { // TODO IE return textarea.selectionEnd = end; }; }); /* vim:ts=4:sts=4:sw=4: * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Ajax.org Code Editor (ACE). * * The Initial Developer of the Original Code is * Ajax.org B.V. * Portions created by the Initial Developer are Copyright (C) 2010 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Fabian Jakobs * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('ace/keyboard/textinput', function(require, exports, module) { var event = require("pilot/event"); var useragent = require("pilot/useragent"); var dom = require("pilot/dom"); var TextInput = function(parentNode, host) { var text = dom.createElement("textarea"); text.style.left = "-10000px"; parentNode.appendChild(text); var PLACEHOLDER = String.fromCharCode(0); sendText(); var inCompostion = false; var copied = false; var tempStyle = ''; function sendText(valueToSend) { if (!copied) { var value = valueToSend || text.value; if (value) { if (value.charCodeAt(value.length-1) == PLACEHOLDER.charCodeAt(0)) { value = value.slice(0, -1); if (value) host.onTextInput(value); } else host.onTextInput(value); } } copied = false; // Safari doesn't fire copy events if no text is selected text.value = PLACEHOLDER; text.select(); } var onTextInput = function(e) { if (useragent.isIE && text.value.charCodeAt(0) > 128) return; setTimeout(function() { if (!inCompostion) sendText(); }, 0); }; var onCompositionStart = function(e) { inCompostion = true; if (!useragent.isIE) { sendText(); text.value = ""; }; host.onCompositionStart(); if (!useragent.isGecko) setTimeout(onCompositionUpdate, 0); }; var onCompositionUpdate = function() { if (!inCompostion) return; host.onCompositionUpdate(text.value); }; var onCompositionEnd = function() { inCompostion = false; host.onCompositionEnd(); setTimeout(function () { sendText(); }, 0); }; var onCopy = function(e) { copied = true; var copyText = host.getCopyText(); if(copyText) text.value = copyText; else e.preventDefault(); text.select(); setTimeout(function () { sendText(); }, 0); }; var onCut = function(e) { copied = true; var copyText = host.getCopyText(); if(copyText) { text.value = copyText; host.onCut(); } else e.preventDefault(); text.select(); setTimeout(function () { sendText(); }, 0); }; event.addCommandKeyListener(text, host.onCommandKey.bind(host)); event.addListener(text, "keypress", onTextInput); if (useragent.isIE) { var keytable = { 13:1, 27:1 }; event.addListener(text, "keyup", function (e) { if (inCompostion && (!text.value || keytable[e.keyCode])) setTimeout(onCompositionEnd, 0); if ((text.value.charCodeAt(0)|0) < 129) { return; }; inCompostion ? onCompositionUpdate() : onCompositionStart(); }); }; event.addListener(text, "textInput", onTextInput); event.addListener(text, "paste", function(e) { // Some browsers support the event.clipboardData API. Use this to get // the pasted content which increases speed if pasting a lot of lines. if (e.clipboardData && e.clipboardData.getData) { sendText(e.clipboardData.getData("text/plain")); e.preventDefault(); } else // If a browser doesn't support any of the things above, use the regular // method to detect the pasted input. { onTextInput(); } }); if (!useragent.isIE) { event.addListener(text, "propertychange", onTextInput); }; if (useragent.isIE) { event.addListener(text, "beforecopy", function(e) { var copyText = host.getCopyText(); if(copyText) clipboardData.setData("Text", copyText); else e.preventDefault(); }); event.addListener(parentNode, "keydown", function(e) { if (e.ctrlKey && e.keyCode == 88) { var copyText = host.getCopyText(); if (copyText) { clipboardData.setData("Text", copyText); host.onCut(); } event.preventDefault(e) } }); } else { event.addListener(text, "copy", onCopy); event.addListener(text, "cut", onCut); } event.addListener(text, "compositionstart", onCompositionStart); if (useragent.isGecko) { event.addListener(text, "text", onCompositionUpdate); }; if (useragent.isWebKit) { event.addListener(text, "keyup", onCompositionUpdate); }; event.addListener(text, "compositionend", onCompositionEnd); event.addListener(text, "blur", function() { host.onBlur(); }); event.addListener(text, "focus", function() { host.onFocus(); text.select(); }); this.focus = function() { host.onFocus(); text.select(); text.focus(); }; this.blur = function() { text.blur(); }; this.getElement = function() { return text; }; this.onContextMenu = function(mousePos, isEmpty){ if (mousePos) { if(!tempStyle) tempStyle = text.style.cssText; text.style.cssText = 'position:fixed; z-index:1000;' + 'left:' + (mousePos.x - 2) + 'px; top:' + (mousePos.y - 2) + 'px;' } if (isEmpty) text.value=''; } this.onContextMenuClose = function(){ setTimeout(function () { if (tempStyle) { text.style.cssText = tempStyle; tempStyle = ''; } sendText(); }, 0); } }; exports.TextInput = TextInput; }); /* vim:ts=4:sts=4:sw=4: * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Ajax.org Code Editor (ACE). * * The Initial Developer of the Original Code is * Ajax.org B.V. * Portions created by the Initial Developer are Copyright (C) 2010 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Fabian Jakobs * Mihai Sucan * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('ace/mouse_handler', function(require, exports, module) { var event = require("pilot/event"); var dom = require("pilot/dom"); var STATE_UNKNOWN = 0; var STATE_SELECT = 1; var STATE_DRAG = 2; var DRAG_TIMER = 250; // milliseconds var DRAG_OFFSET = 5; // pixels var MouseHandler = function(editor) { this.editor = editor; event.addListener(editor.container, "mousedown", function(e) { editor.focus(); return event.preventDefault(e); }); event.addListener(editor.container, "selectstart", function(e) { return event.preventDefault(e); }); var mouseTarget = editor.renderer.getMouseEventTarget(); event.addListener(mouseTarget, "mousedown", this.onMouseDown.bind(this)); event.addMultiMouseDownListener(mouseTarget, 0, 2, 500, this.onMouseDoubleClick.bind(this)); event.addMultiMouseDownListener(mouseTarget, 0, 3, 600, this.onMouseTripleClick.bind(this)); event.addMultiMouseDownListener(mouseTarget, 0, 4, 600, this.onMouseQuadClick.bind(this)); event.addMouseWheelListener(mouseTarget, this.onMouseWheel.bind(this)); }; (function() { this.$scrollSpeed = 1; this.setScrollSpeed = function(speed) { this.$scrollSpeed = speed; }; this.getScrollSpeed = function() { return this.$scrollSpeed; }; this.$getEventPosition = function(e) { var pageX = event.getDocumentX(e); var pageY = event.getDocumentY(e); var pos = this.editor.renderer.screenToTextCoordinates(pageX, pageY); pos.row = Math.max(0, Math.min(pos.row, this.editor.session.getLength()-1)); return pos; }; this.$distance = function(ax, ay, bx, by) { return Math.sqrt(Math.pow(bx - ax, 2) + Math.pow(by - ay, 2)); }; this.onMouseDown = function(e) { var pageX = event.getDocumentX(e); var pageY = event.getDocumentY(e); var pos = this.$getEventPosition(e); var editor = this.editor; var self = this; var selectionRange = editor.getSelectionRange(); var selectionEmpty = selectionRange.isEmpty(); var state = STATE_UNKNOWN; var inSelection = false; var button = event.getButton(e); if (button !== 0) { if (selectionEmpty) { editor.moveCursorToPosition(pos); } if(button == 2) { editor.textInput.onContextMenu({x: pageX, y: pageY}, selectionEmpty); event.capture(editor.container, function(){}, editor.textInput.onContextMenuClose); } return; } else { inSelection = !editor.getReadOnly() && !selectionEmpty && selectionRange.contains(pos.row, pos.column); } if (!inSelection) { // Directly pick STATE_SELECT, since the user is not clicking inside // a selection. onStartSelect(pos); } editor.renderer.scrollCursorIntoView(); var mousePageX, mousePageY; var overwrite = editor.getOverwrite(); var mousedownTime = (new Date()).getTime(); var dragCursor, dragRange; var onMouseSelection = function(e) { mousePageX = event.getDocumentX(e); mousePageY = event.getDocumentY(e); }; var onMouseSelectionEnd = function() { clearInterval(timerId); if (state == STATE_UNKNOWN) onStartSelect(pos); else if (state == STATE_DRAG) onMouseDragSelectionEnd(); self.$clickSelection = null; state = STATE_UNKNOWN; }; var onMouseDragSelectionEnd = function() { dom.removeCssClass(editor.container, "ace_dragging"); editor.session.removeMarker(dragSelectionMarker); if (!self.$clickSelection) { if (!dragCursor) { editor.moveCursorToPosition(pos); editor.selection.clearSelection(pos.row, pos.column); } } if (!dragCursor) return; if (dragRange.contains(dragCursor.row, dragCursor.column)) { dragCursor = null; return; } editor.clearSelection(); var newRange = editor.moveText(dragRange, dragCursor); if (!newRange) { dragCursor = null; return; } editor.selection.setSelectionRange(newRange); }; var onSelectionInterval = function() { if (mousePageX === undefined || mousePageY === undefined) return; if (state == STATE_UNKNOWN) { var distance = self.$distance(pageX, pageY, mousePageX, mousePageY); var time = (new Date()).getTime(); if (distance > DRAG_OFFSET) { state = STATE_SELECT; var cursor = editor.renderer.screenToTextCoordinates(mousePageX, mousePageY); cursor.row = Math.max(0, Math.min(cursor.row, editor.session.getLength()-1)); onStartSelect(cursor); } else if ((time - mousedownTime) > DRAG_TIMER) { state = STATE_DRAG; dragRange = editor.getSelectionRange(); var style = editor.getSelectionStyle(); dragSelectionMarker = editor.session.addMarker(dragRange, "ace_selection", style); editor.clearSelection(); dom.addCssClass(editor.container, "ace_dragging"); } } if (state == STATE_DRAG) onDragSelectionInterval(); else if (state == STATE_SELECT) onUpdateSelectionInterval(); }; function onStartSelect(pos) { if (e.shiftKey) editor.selection.selectToPosition(pos) else { if (!self.$clickSelection) { editor.moveCursorToPosition(pos); editor.selection.clearSelection(pos.row, pos.column); } } state = STATE_SELECT; } var onUpdateSelectionInterval = function() { var cursor = editor.renderer.screenToTextCoordinates(mousePageX, mousePageY); cursor.row = Math.max(0, Math.min(cursor.row, editor.session.getLength()-1)); if (self.$clickSelection) { if (self.$clickSelection.contains(cursor.row, cursor.column)) { editor.selection.setSelectionRange(self.$clickSelection); } else { if (self.$clickSelection.compare(cursor.row, cursor.column) == -1) { var anchor = self.$clickSelection.end; } else { var anchor = self.$clickSelection.start; } editor.selection.setSelectionAnchor(anchor.row, anchor.column); editor.selection.selectToPosition(cursor); } } else { editor.selection.selectToPosition(cursor); } editor.renderer.scrollCursorIntoView(); }; var onDragSelectionInterval = function() { dragCursor = editor.renderer.screenToTextCoordinates(mousePageX, mousePageY); dragCursor.row = Math.max(0, Math.min(dragCursor.row, editor.session.getLength() - 1)); editor.moveCursorToPosition(dragCursor); }; event.capture(editor.container, onMouseSelection, onMouseSelectionEnd); var timerId = setInterval(onSelectionInterval, 20); return event.preventDefault(e); }; this.onMouseDoubleClick = function(e) { var pos = this.$getEventPosition(e); this.editor.moveCursorToPosition(pos); this.editor.selection.selectWord(); this.$clickSelection = this.editor.getSelectionRange(); }; this.onMouseTripleClick = function(e) { var pos = this.$getEventPosition(e); this.editor.moveCursorToPosition(pos); this.editor.selection.selectLine(); this.$clickSelection = this.editor.getSelectionRange(); }; this.onMouseQuadClick = function(e) { this.editor.selectAll(); this.$clickSelection = this.editor.getSelectionRange(); }; this.onMouseWheel = function(e) { var speed = this.$scrollSpeed * 2; this.editor.renderer.scrollBy(e.wheelX * speed, e.wheelY * speed); return event.preventDefault(e); }; }).call(MouseHandler.prototype); exports.MouseHandler = MouseHandler; }); /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Ajax.org Code Editor (ACE). * * The Initial Developer of the Original Code is * Ajax.org B.V. * Portions created by the Initial Developer are Copyright (C) 2010 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Fabian Jakobs * Julian Viereck * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('ace/keyboard/keybinding', function(require, exports, module) { var useragent = require("pilot/useragent"); var keyUtil = require("pilot/keys"); var event = require("pilot/event"); var settings = require("pilot/settings").settings; var canon = require("pilot/canon"); require("ace/commands/default_commands"); var KeyBinding = function(editor) { this.$editor = editor; this.$data = { }; this.$keyboardHandler = null; }; (function() { this.setKeyboardHandler = function(keyboardHandler) { if (this.$keyboardHandler != keyboardHandler) { this.$data = { }; this.$keyboardHandler = keyboardHandler; } }; this.getKeyboardHandler = function() { return this.$keyboardHandler; }; this.$callKeyboardHandler = function (e, hashId, keyOrText, keyCode) { var env = {editor: this.$editor}, toExecute; if (this.$keyboardHandler) { toExecute = this.$keyboardHandler.handleKeyboard(this.$data, hashId, keyOrText, keyCode, e); } // If there is nothing to execute yet, then use the default keymapping. if (!toExecute || !toExecute.command) { if (hashId != 0 || keyCode != 0) { toExecute = { command: canon.findKeyCommand(env, "editor", hashId, keyOrText) } } else { toExecute = { command: "inserttext", args: { text: keyOrText } } } } if (toExecute) { var success = canon.exec(toExecute.command, env, "editor", toExecute.args); if (success) { return event.stopEvent(e); } } }; this.onCommandKey = function(e, hashId, keyCode) { var keyString = keyUtil.keyCodeToString(keyCode); this.$callKeyboardHandler(e, hashId, keyString, keyCode); }; this.onTextInput = function(text) { this.$callKeyboardHandler({}, 0, text, 0); } }).call(KeyBinding.prototype); exports.KeyBinding = KeyBinding; }); /* vim:ts=4:sts=4:sw=4: * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Ajax.org Code Editor (ACE). * * The Initial Developer of the Original Code is * Ajax.org B.V. * Portions created by the Initial Developer are Copyright (C) 2010 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Fabian Jakobs * Julian Viereck * Mihai Sucan * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('ace/commands/default_commands', function(require, exports, module) { var lang = require("pilot/lang"); var canon = require("pilot/canon"); function bindKey(win, mac) { return { win: win, mac: mac, sender: "editor" }; } canon.addCommand({ name: "null", exec: function(env, args, request) { } }); canon.addCommand({ name: "selectall", bindKey: bindKey("Ctrl-A", "Command-A"), exec: function(env, args, request) { env.editor.selectAll(); } }); canon.addCommand({ name: "removeline", bindKey: bindKey("Ctrl-D", "Command-D"), exec: function(env, args, request) { env.editor.removeLines(); } }); canon.addCommand({ name: "gotoline", bindKey: bindKey("Ctrl-L", "Command-L"), exec: function(env, args, request) { var line = parseInt(prompt("Enter line number:")); if (!isNaN(line)) { env.editor.gotoLine(line); } } }); canon.addCommand({ name: "togglecomment", bindKey: bindKey("Ctrl-7", "Command-7"), exec: function(env, args, request) { env.editor.toggleCommentLines(); } }); canon.addCommand({ name: "findnext", bindKey: bindKey("Ctrl-K", "Command-G"), exec: function(env, args, request) { env.editor.findNext(); } }); canon.addCommand({ name: "findprevious", bindKey: bindKey("Ctrl-Shift-K", "Command-Shift-G"), exec: function(env, args, request) { env.editor.findPrevious(); } }); canon.addCommand({ name: "find", bindKey: bindKey("Ctrl-F", "Command-F"), exec: function(env, args, request) { var needle = prompt("Find:"); env.editor.find(needle); } }); canon.addCommand({ name: "replace", bindKey: bindKey("Ctrl-R", "Command-Option-F"), exec: function(env, args, request) { var needle = prompt("Find:"); if (!needle) return; var replacement = prompt("Replacement:"); if (!replacement) return; env.editor.replace(replacement, {needle: needle}); } }); canon.addCommand({ name: "replaceall", bindKey: bindKey("Ctrl-Shift-R", "Command-Shift-Option-F"), exec: function(env, args, request) { var needle = prompt("Find:"); if (!needle) return; var replacement = prompt("Replacement:"); if (!replacement) return; env.editor.replaceAll(replacement, {needle: needle}); } }); canon.addCommand({ name: "undo", bindKey: bindKey("Ctrl-Z", "Command-Z"), exec: function(env, args, request) { env.editor.undo(); } }); canon.addCommand({ name: "redo", bindKey: bindKey("Ctrl-Shift-Z|Ctrl-Y", "Command-Shift-Z|Command-Y"), exec: function(env, args, request) { env.editor.redo(); } }); canon.addCommand({ name: "overwrite", bindKey: bindKey("Insert", "Insert"), exec: function(env, args, request) { env.editor.toggleOverwrite(); } }); canon.addCommand({ name: "copylinesup", bindKey: bindKey("Ctrl-Alt-Up", "Command-Option-Up"), exec: function(env, args, request) { env.editor.copyLinesUp(); } }); canon.addCommand({ name: "movelinesup", bindKey: bindKey("Alt-Up", "Option-Up"), exec: function(env, args, request) { env.editor.moveLinesUp(); } }); canon.addCommand({ name: "selecttostart", bindKey: bindKey("Alt-Shift-Up", "Command-Shift-Up"), exec: function(env, args, request) { env.editor.getSelection().selectFileStart(); } }); canon.addCommand({ name: "gotostart", bindKey: bindKey("Ctrl-Home|Ctrl-Up", "Command-Home|Command-Up"), exec: function(env, args, request) { env.editor.navigateFileStart(); } }); canon.addCommand({ name: "selectup", bindKey: bindKey("Shift-Up", "Shift-Up"), exec: function(env, args, request) { env.editor.getSelection().selectUp(); } }); canon.addCommand({ name: "golineup", bindKey: bindKey("Up", "Up|Ctrl-P"), exec: function(env, args, request) { env.editor.navigateUp(args.times); } }); canon.addCommand({ name: "copylinesdown", bindKey: bindKey("Ctrl-Alt-Down", "Command-Option-Down"), exec: function(env, args, request) { env.editor.copyLinesDown(); } }); canon.addCommand({ name: "movelinesdown", bindKey: bindKey("Alt-Down", "Option-Down"), exec: function(env, args, request) { env.editor.moveLinesDown(); } }); canon.addCommand({ name: "selecttoend", bindKey: bindKey("Alt-Shift-Down", "Command-Shift-Down"), exec: function(env, args, request) { env.editor.getSelection().selectFileEnd(); } }); canon.addCommand({ name: "gotoend", bindKey: bindKey("Ctrl-End|Ctrl-Down", "Command-End|Command-Down"), exec: function(env, args, request) { env.editor.navigateFileEnd(); } }); canon.addCommand({ name: "selectdown", bindKey: bindKey("Shift-Down", "Shift-Down"), exec: function(env, args, request) { env.editor.getSelection().selectDown(); } }); canon.addCommand({ name: "golinedown", bindKey: bindKey("Down", "Down|Ctrl-N"), exec: function(env, args, request) { env.editor.navigateDown(args.times); } }); canon.addCommand({ name: "selectwordleft", bindKey: bindKey("Ctrl-Shift-Left", "Option-Shift-Left"), exec: function(env, args, request) { env.editor.getSelection().selectWordLeft(); } }); canon.addCommand({ name: "gotowordleft", bindKey: bindKey("Ctrl-Left", "Option-Left"), exec: function(env, args, request) { env.editor.navigateWordLeft(); } }); canon.addCommand({ name: "selecttolinestart", bindKey: bindKey("Alt-Shift-Left", "Command-Shift-Left"), exec: function(env, args, request) { env.editor.getSelection().selectLineStart(); } }); canon.addCommand({ name: "gotolinestart", bindKey: bindKey("Alt-Left|Home", "Command-Left|Home|Ctrl-A"), exec: function(env, args, request) { env.editor.navigateLineStart(); } }); canon.addCommand({ name: "selectleft", bindKey: bindKey("Shift-Left", "Shift-Left"), exec: function(env, args, request) { env.editor.getSelection().selectLeft(); } }); canon.addCommand({ name: "gotoleft", bindKey: bindKey("Left", "Left|Ctrl-B"), exec: function(env, args, request) { env.editor.navigateLeft(args.times); } }); canon.addCommand({ name: "selectwordright", bindKey: bindKey("Ctrl-Shift-Right", "Option-Shift-Right"), exec: function(env, args, request) { env.editor.getSelection().selectWordRight(); } }); canon.addCommand({ name: "gotowordright", bindKey: bindKey("Ctrl-Right", "Option-Right"), exec: function(env, args, request) { env.editor.navigateWordRight(); } }); canon.addCommand({ name: "selecttolineend", bindKey: bindKey("Alt-Shift-Right", "Command-Shift-Right"), exec: function(env, args, request) { env.editor.getSelection().selectLineEnd(); } }); canon.addCommand({ name: "gotolineend", bindKey: bindKey("Alt-Right|End", "Command-Right|End|Ctrl-E"), exec: function(env, args, request) { env.editor.navigateLineEnd(); } }); canon.addCommand({ name: "selectright", bindKey: bindKey("Shift-Right", "Shift-Right"), exec: function(env, args, request) { env.editor.getSelection().selectRight(); } }); canon.addCommand({ name: "gotoright", bindKey: bindKey("Right", "Right|Ctrl-F"), exec: function(env, args, request) { env.editor.navigateRight(args.times); } }); canon.addCommand({ name: "selectpagedown", bindKey: bindKey("Shift-PageDown", "Shift-PageDown"), exec: function(env, args, request) { env.editor.selectPageDown(); } }); canon.addCommand({ name: "pagedown", bindKey: bindKey(null, "PageDown"), exec: function(env, args, request) { env.editor.scrollPageDown(); } }); canon.addCommand({ name: "gotopagedown", bindKey: bindKey("PageDown", "Option-PageDown|Ctrl-V"), exec: function(env, args, request) { env.editor.gotoPageDown(); } }); canon.addCommand({ name: "selectpageup", bindKey: bindKey("Shift-PageUp", "Shift-PageUp"), exec: function(env, args, request) { env.editor.selectPageUp(); } }); canon.addCommand({ name: "pageup", bindKey: bindKey(null, "PageUp"), exec: function(env, args, request) { env.editor.scrollPageUp(); } }); canon.addCommand({ name: "gotopageup", bindKey: bindKey("PageUp", "Option-PageUp"), exec: function(env, args, request) { env.editor.gotoPageUp(); } }); canon.addCommand({ name: "selectlinestart", bindKey: bindKey("Shift-Home", "Shift-Home"), exec: function(env, args, request) { env.editor.getSelection().selectLineStart(); } }); canon.addCommand({ name: "selectlineend", bindKey: bindKey("Shift-End", "Shift-End"), exec: function(env, args, request) { env.editor.getSelection().selectLineEnd(); } }); canon.addCommand({ name: "del", bindKey: bindKey("Delete", "Delete|Ctrl-D"), exec: function(env, args, request) { env.editor.removeRight(); } }); canon.addCommand({ name: "backspace", bindKey: bindKey( "Ctrl-Backspace|Command-Backspace|Option-Backspace|Shift-Backspace|Backspace", "Ctrl-Backspace|Command-Backspace|Shift-Backspace|Backspace|Ctrl-H" ), exec: function(env, args, request) { env.editor.removeLeft(); } }); canon.addCommand({ name: "removetolinestart", bindKey: bindKey(null, "Option-Backspace"), exec: function(env, args, request) { env.editor.removeToLineStart(); } }); canon.addCommand({ name: "removetolineend", bindKey: bindKey(null, "Ctrl-K"), exec: function(env, args, request) { env.editor.removeToLineEnd(); } }); canon.addCommand({ name: "removewordleft", bindKey: bindKey(null, "Alt-Backspace|Ctrl-Alt-Backspace"), exec: function(env, args, request) { env.editor.removeWordLeft(); } }); canon.addCommand({ name: "removewordright", bindKey: bindKey(null, "Alt-Delete"), exec: function(env, args, request) { env.editor.removeWordRight(); } }); canon.addCommand({ name: "outdent", bindKey: bindKey("Shift-Tab", "Shift-Tab"), exec: function(env, args, request) { env.editor.blockOutdent(); } }); canon.addCommand({ name: "indent", bindKey: bindKey("Tab", "Tab"), exec: function(env, args, request) { env.editor.indent(); } }); canon.addCommand({ name: "inserttext", exec: function(env, args, request) { env.editor.insert(lang.stringRepeat(args.text || "", args.times || 1)); } }); canon.addCommand({ name: "splitline", bindKey: bindKey(null, "Ctrl-O"), exec: function(env, args, request) { env.editor.splitLine(); } }); canon.addCommand({ name: "transposeletters", bindKey: bindKey("Ctrl-T", "Ctrl-T"), exec: function(env, args, request) { env.editor.transposeLetters(); } }); });/* vim:ts=4:sts=4:sw=4: * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Ajax.org Code Editor (ACE). * * The Initial Developer of the Original Code is * Ajax.org B.V. * Portions created by the Initial Developer are Copyright (C) 2010 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Fabian Jakobs * Mihai Sucan * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('ace/edit_session', function(require, exports, module) { var oop = require("pilot/oop"); var lang = require("pilot/lang"); var EventEmitter = require("pilot/event_emitter").EventEmitter; var Selection = require("ace/selection").Selection; var TextMode = require("ace/mode/text").Mode; var Range = require("ace/range").Range; var Document = require("ace/document").Document; var EditSession = function(text, mode) { this.$modified = true; this.$breakpoints = []; this.$frontMarkers = {}; this.$backMarkers = {}; this.$markerId = 1; this.$wrapData = []; if (text instanceof Document) { this.setDocument(text); } else { this.setDocument(new Document(text)); } this.selection = new Selection(this); if (mode) this.setMode(mode); }; (function() { oop.implement(this, EventEmitter); this.setDocument = function(doc) { if (this.doc) throw new Error("Document is already set"); this.doc = doc; doc.on("change", this.onChange.bind(this)); }; this.getDocument = function() { return this.doc; }; this.onChange = function(e) { var delta = e.data; this.$modified = true; if (!this.$fromUndo && this.$undoManager && !delta.ignore) { this.$deltas.push(delta); this.$informUndoManager.schedule(); } this.$updateWrapDataOnChange(e); this._dispatchEvent("change", e); }; this.setValue = function(text) { this.doc.setValue(text); this.$deltas = []; this.getUndoManager().reset(); }; this.getValue = this.toString = function() { return this.doc.getValue(); }; this.getSelection = function() { return this.selection; }; this.setUndoManager = function(undoManager) { this.$undoManager = undoManager; this.$deltas = []; if (this.$informUndoManager) { this.$informUndoManager.cancel(); } if (undoManager) { var self = this; this.$informUndoManager = lang.deferredCall(function() { if (self.$deltas.length > 0) undoManager.execute({ action : "aceupdate", args : [self.$deltas, self] }); self.$deltas = []; }); } }; this.$defaultUndoManager = { undo: function() {}, redo: function() {}, reset: function() {} }; this.getUndoManager = function() { return this.$undoManager || this.$defaultUndoManager; }, this.getTabString = function() { if (this.getUseSoftTabs()) { return lang.stringRepeat(" ", this.getTabSize()); } else { return "\t"; } }; this.$useSoftTabs = true; this.setUseSoftTabs = function(useSoftTabs) { if (this.$useSoftTabs === useSoftTabs) return; this.$useSoftTabs = useSoftTabs; }; this.getUseSoftTabs = function() { return this.$useSoftTabs; }; this.$tabSize = 4; this.setTabSize = function(tabSize) { if (isNaN(tabSize) || this.$tabSize === tabSize) return; this.$modified = true; this.$tabSize = tabSize; this._dispatchEvent("changeTabSize"); }; this.getTabSize = function() { return this.$tabSize; }; this.isTabStop = function(position) { return this.$useSoftTabs && (position.column % this.$tabSize == 0); }; this.$overwrite = false; this.setOverwrite = function(overwrite) { if (this.$overwrite == overwrite) return; this.$overwrite = overwrite; this._dispatchEvent("changeOverwrite"); }; this.getOverwrite = function() { return this.$overwrite; }; this.toggleOverwrite = function() { this.setOverwrite(!this.$overwrite); }; this.getBreakpoints = function() { return this.$breakpoints; }; this.setBreakpoints = function(rows) { this.$breakpoints = []; for (var i=0; i 0) { inToken = !!line.charAt(column - 1).match(this.tokenRe); } if (!inToken) { inToken = !!line.charAt(column).match(this.tokenRe); } var re = inToken ? this.tokenRe : this.nonTokenRe; var start = column; if (start > 0) { do { start--; } while (start >= 0 && line.charAt(start).match(re)); start++; } var end = column; while (end < line.length && line.charAt(end).match(re)) { end++; } return new Range(row, start, row, end); }; this.setNewLineMode = function(newLineMode) { this.doc.setNewLineMode(newLineMode); }; this.getNewLineMode = function() { return this.doc.getNewLineMode(); }; this.$useWorker = true; this.setUseWorker = function(useWorker) { if (this.$useWorker == useWorker) return; if (useWorker && !this.$worker && window.Worker) this.$worker = mode.createWorker(this); if (!useWorker && this.$worker) { this.$worker.terminate(); this.$worker = null; } }; this.getUseWorker = function() { return this.$useWorker; }; this.$mode = null; this.setMode = function(mode) { if (this.$mode === mode) return; if (this.$worker) this.$worker.terminate(); if (this.$useWorker && window.Worker && !require.noWorker) this.$worker = mode.createWorker(this); else this.$worker = null; this.$mode = mode; this._dispatchEvent("changeMode"); }; this.getMode = function() { if (!this.$mode) { this.$mode = new TextMode(); } return this.$mode; }; this.$scrollTop = 0; this.setScrollTopRow = function(scrollTopRow) { if (this.$scrollTop === scrollTopRow) return; this.$scrollTop = scrollTopRow; this._dispatchEvent("changeScrollTop"); }; this.getScrollTopRow = function() { return this.$scrollTop; }; this.getWidth = function() { this.$computeWidth(); return this.width; }; this.getScreenWidth = function() { this.$computeWidth(); return this.screenWidth; }; this.$computeWidth = function(force) { if (this.$modified || force) { this.$modified = false; var lines = this.doc.getAllLines(); var longestLine = 0; var longestScreenLine = 0; var tabSize = this.getTabSize(); for ( var i = 0; i < lines.length; i++) { var len = lines[i].length; longestLine = Math.max(longestLine, len); lines[i].replace(/\t/g, function(m) { len += tabSize-1; return m; }); longestScreenLine = Math.max(longestScreenLine, len); } this.width = longestLine; if (this.$useWrapMode) { this.screenWidth = this.$wrapLimit; } else { this.screenWidth = longestScreenLine; } } }; /** * Get a verbatim copy of the given line as it is in the document */ this.getLine = function(row) { return this.doc.getLine(row); }; /** * Get a line as it is displayed on screen. Tabs are replaced by spaces. */ this.getDisplayLine = function(row) { var tab = new Array(this.getTabSize()+1).join(" "); return this.doc.getLine(row).replace(/\t/g, tab); }; this.getLines = function(firstRow, lastRow) { return this.doc.getLines(firstRow, lastRow); }; this.getLength = function() { return this.doc.getLength(); }; this.getTextRange = function(range) { return this.doc.getTextRange(range); }; this.findMatchingBracket = function(position) { if (position.column == 0) return null; var charBeforeCursor = this.getLine(position.row).charAt(position.column-1); if (charBeforeCursor == "") return null; var match = charBeforeCursor.match(/([\(\[\{])|([\)\]\}])/); if (!match) { return null; } if (match[1]) { return this.$findClosingBracket(match[1], position); } else { return this.$findOpeningBracket(match[2], position); } }; this.$brackets = { ")": "(", "(": ")", "]": "[", "[": "]", "{": "}", "}": "{" }; this.$findOpeningBracket = function(bracket, position) { var openBracket = this.$brackets[bracket]; var column = position.column - 2; var row = position.row; var depth = 1; var line = this.getLine(row); while (true) { while(column >= 0) { var ch = line.charAt(column); if (ch == openBracket) { depth -= 1; if (depth == 0) { return {row: row, column: column}; } } else if (ch == bracket) { depth +=1; } column -= 1; } row -=1; if (row < 0) break; var line = this.getLine(row); var column = line.length-1; } return null; }; this.$findClosingBracket = function(bracket, position) { var closingBracket = this.$brackets[bracket]; var column = position.column; var row = position.row; var depth = 1; var line = this.getLine(row); var lineCount = this.getLength(); while (true) { while(column < line.length) { var ch = line.charAt(column); if (ch == closingBracket) { depth -= 1; if (depth == 0) { return {row: row, column: column}; } } else if (ch == bracket) { depth +=1; } column += 1; } row +=1; if (row >= lineCount) break; var line = this.getLine(row); var column = 0; } return null; }; this.insert = function(position, text) { return this.doc.insert(position, text); }; this.remove = function(range) { return this.doc.remove(range); }; this.undoChanges = function(deltas) { if (!deltas.length) return; this.$fromUndo = true; this.doc.revertDeltas(deltas); this.$fromUndo = false; this.$setUndoSelection(deltas, true); }, this.redoChanges = function(deltas) { if (!deltas.length) return; this.$fromUndo = true; this.doc.applyDeltas(deltas); this.$fromUndo = false; this.$setUndoSelection(deltas, false); }, this.$setUndoSelection = function(deltas, isUndo) { // invert deltas is they are an undo if (isUndo) deltas = deltas.map(function(delta) { var d = { range: delta.range } if (delta.action == "insertText" || delta.action == "insertLines") d.action = "removeText" else d.action = "insertText" return d; }).reverse(); var actions = [{}]; // collapse insert and remove operations for (var i=0; i= this.doc.getLength()-1) return 0; var removed = this.doc.removeLines(firstRow, lastRow); this.doc.insertLines(firstRow+1, removed); return 1; }; this.duplicateLines = function(firstRow, lastRow) { var firstRow = this.$clipRowToDocument(firstRow); var lastRow = this.$clipRowToDocument(lastRow); var lines = this.getLines(firstRow, lastRow); this.doc.insertLines(firstRow, lines); var addedRows = lastRow - firstRow + 1; return addedRows; }; this.$clipRowToDocument = function(row) { return Math.max(0, Math.min(row, this.doc.getLength()-1)); }; // WRAPMODE this.$wrapLimit = 80; this.$useWrapMode = false; this.$wrapLimitRange = { min : null, max : null }; this.setUseWrapMode = function(useWrapMode) { if (useWrapMode != this.$useWrapMode) { this.$useWrapMode = useWrapMode; this.$modified = true; // If wrapMode is activaed, the wrapData array has to be initialized. if (useWrapMode) { var len = this.getLength(); this.$wrapData = []; for (i = 0; i < len; i++) { this.$wrapData.push([]); } this.$updateWrapData(0, len - 1); } this._dispatchEvent("changeWrapMode"); } }; this.getUseWrapMode = function() { return this.$useWrapMode; }; // Allow the wrap limit to move freely between min and max. Either // parameter can be null to allow the wrap limit to be unconstrained // in that direction. Or set both parameters to the same number to pin // the limit to that value. this.setWrapLimitRange = function(min, max) { if (this.$wrapLimitRange.min !== min || this.$wrapLimitRange.max !== max) { this.$wrapLimitRange.min = min; this.$wrapLimitRange.max = max; this.$modified = true; // This will force a recalculation of the wrap limit this._dispatchEvent("changeWrapMode"); } }; // This should generally only be called by the renderer when a resize // is detected. this.adjustWrapLimit = function(desiredLimit) { var wrapLimit = this.$constrainWrapLimit(desiredLimit); if (wrapLimit != this.$wrapLimit && wrapLimit > 0) { this.$wrapLimit = wrapLimit; this.$modified = true; if (this.$useWrapMode) { this.$updateWrapData(0, this.getLength() - 1); this._dispatchEvent("changeWrapLimit"); } return true; } return false; }; this.$constrainWrapLimit = function(wrapLimit) { var min = this.$wrapLimitRange.min; if (min) wrapLimit = Math.max(min, wrapLimit); var max = this.$wrapLimitRange.max; if (max) wrapLimit = Math.min(max, wrapLimit); // What would a limit of 0 even mean? return Math.max(1, wrapLimit); }; this.getWrapLimit = function() { return this.$wrapLimit; }; this.getWrapLimitRange = function() { // Avoid unexpected mutation by returning a copy return { min : this.$wrapLimitRange.min, max : this.$wrapLimitRange.max }; }; this.$updateWrapDataOnChange = function(e) { if (!this.$useWrapMode) { return; } var len; var action = e.data.action; var firstRow = e.data.range.start.row, lastRow = e.data.range.end.row; if (action.indexOf("Lines") != -1) { if (action == "insertLines") { lastRow = firstRow + (e.data.lines.length); } else { lastRow = firstRow; } len = e.data.lines.length; } else { len = lastRow - firstRow; } if (len != 0) { if (action.indexOf("remove") != -1) { this.$wrapData.splice(firstRow, len); lastRow = firstRow; } else { var args = [firstRow, 0]; for (var i = 0; i < len; i++) args.push([]); this.$wrapData.splice.apply(this.$wrapData, args); } } if (this.$wrapData.length != this.doc.$lines.length) { console.error("The length of doc.$lines and $wrapData have to be the same!"); } this.$updateWrapData(firstRow, lastRow); }; this.$updateWrapData = function(firstRow, lastRow) { var lines = this.doc.getAllLines(); var tabSize = this.getTabSize(); var wrapData = this.$wrapData; var wrapLimit = this.$wrapLimit; for (var row = firstRow; row <= lastRow; row++) { wrapData[row] = this.$computeWrapSplits(lines[row], wrapLimit, tabSize); } }; // "Tokens" var CHAR = 1, CHAR_EXT = 2, SPACE = 3, TAB = 4, TAB_SPACE = 5; this.$computeWrapSplits = function(textLine, wrapLimit, tabSize) { textLine = textLine.trimRight(); if (textLine.length == 0) { return []; } var tabSize = this.getTabSize(); var splits = []; var tokens = this.$getDisplayTokens(textLine); var displayLength = tokens.length; var lastSplit = 0, lastDocSplit = 0; function addSplit(screenPos) { var displayed = tokens.slice(lastSplit, screenPos); // The document size is the current size - the extra width for tabs // and multipleWidth characters. var len = displayed.length; displayed.join(""). // Get all the tabs. replace(/4/g, function(m) { len -= tabSize - 1; }). // Get all the multipleWidth characters. replace(/2/g, function(m) { len -= 1; }); lastDocSplit += len; splits.push(lastDocSplit); lastSplit = screenPos; } while (displayLength - lastSplit > wrapLimit) { // This is, where the split should be. var split = lastSplit + wrapLimit; // If there is a space or tab at this split position. if (tokens[split] >= SPACE) { // Include all following spaces + tabs in this split as well. while (tokens[split] >= SPACE) { split ++; } addSplit(split); } else { // Search for the first non space/tab token. for (split; split != lastSplit - 1; split--) { if (tokens[split] >= SPACE) { split++; break; } } // If we found one, then add the split. if (split > lastSplit) { addSplit(split); } // No space or tab around? Well, force a split then. else { addSplit(lastSplit + wrapLimit); } } } return splits; } this.$getDisplayTokens = function(str) { var arr = []; var tabSize = this.getTabSize(); for (var i = 0; i < str.length; i++) { var c = str.charCodeAt(i); // Tab if (c == 9) { arr.push(TAB); for (var n = 1; n < tabSize; n++) { arr.push(TAB_SPACE); } } // Space else if(c == 32) { arr.push(SPACE); } // CJK characters else if ( c >= 0x3040 && c <= 0x309F || // Hiragana c >= 0x30A0 && c <= 0x30FF || // Katakana c >= 0x4E00 && c <= 0x9FFF || // Single CJK ideographs c >= 0xF900 && c <= 0xFAFF || c >= 0x3400 && c <= 0x4DBF ) { arr.push(CHAR, CHAR_EXT); } else { arr.push(CHAR); } } return arr; } /** * Calculates the width of the a string on the screen while assuming that * the string starts at the first column on the screen. * * @param string str String to calculate the screen width of * @return int number of columns for str on screen. */ this.$getStringScreenWidth = function(str) { var screenColumn = 0; var tabSize = this.getTabSize(); for (var i=0; i= 0x3040 && c <= 0x309F || // Hiragana c >= 0x30A0 && c <= 0x30FF || // Katakana c >= 0x4E00 && c <= 0x9FFF || // Single CJK ideographs c >= 0xF900 && c <= 0xFAFF || c >= 0x3400 && c <= 0x4DBF ) { screenColumn += 2; } else { screenColumn += 1; } } return screenColumn; } this.getRowHeight = function(config, row) { var rows; if (!this.$useWrapMode || !this.$wrapData[row]) { rows = 1; } else { rows = this.$wrapData[row].length + 1; } return rows * config.lineHeight; } this.getScreenLastRowColumn = function(screenRow, returnDocPosition) { if (!this.$useWrapMode) { return this.$getStringScreenWidth(this.getLine(screenRow)); } var rowData = this.$screenToDocumentRow(screenRow); var docRow = rowData[0], row = rowData[1]; var start, end; if (this.$wrapData[docRow][row]) { start = (this.$wrapData[docRow][row - 1] || 0); end = this.$wrapData[docRow][row]; returnDocPosition && end--; } else { end = this.getLine(docRow).length; start = (this.$wrapData[docRow][row - 1] || 0); } if (!returnDocPosition) { return this.$getStringScreenWidth(this.getLine(docRow).substring(start, end)); } else { return end; } }; this.getDocumentLastRowColumn = function(docRow, docColumn) { if (!this.$useWrapMode) { return this.getLine(docRow).length; } var screenRow = this.documentToScreenRow(docRow, docColumn); return this.getScreenLastRowColumn(screenRow, true); } this.getScreenFirstRowColumn = function(screenRow) { if (!this.$useWrapMode) { return 0; } var rowData = this.$screenToDocumentRow(screenRow); var docRow = rowData[0], row = rowData[1]; return this.$wrapData[docRow][row - 1] || 0; }; this.getRowSplitData = function(row) { if (!this.$useWrapMode) { return undefined; } else { return this.$wrapData[row]; } }; /** * * @returns array * - array[0]: The documentRow equivalent. * - array[1]: The screenRowOffset to the first documentRow on the screen. */ this.$screenToDocumentRow = function(row) { if (!this.$useWrapMode) { return [row, 0]; } var wrapData = this.$wrapData, linesCount = this.getLength(); var docRow = 0; while (docRow < linesCount && row >= wrapData[docRow].length + 1) { row -= wrapData[docRow].length + 1; docRow ++; } return [docRow, row]; }; this.screenToDocumentRow = function(screenRow) { return this.$screenToDocumentRow(screenRow)[0]; }; this.screenToDocumentColumn = function(screenRow, screenColumn) { return this.screenToDocumentPosition(screenRow, screenColumn).column; }; this.screenToDocumentPosition = function(row, column) { var line; var docRow; var docColumn; var remaining = column; var linesCount = this.getLength(); if (!this.$useWrapMode) { docRow = row >= linesCount? linesCount-1 : (row < 0 ? 0 : row); row = 0; docColumn = 0; line = this.getLine(docRow); } else { var wrapData = this.$wrapData; var docRow = 0; while (docRow < linesCount && row >= wrapData[docRow].length + 1) { row -= wrapData[docRow].length + 1; docRow ++; } if (docRow >= linesCount) { docRow = linesCount-1 row = wrapData[docRow].length; } docColumn = wrapData[docRow][row - 1] || 0; line = this.getLine(docRow).substring(docColumn); } var tabSize = this.getTabSize(); for(var i = 0; i < line.length; i++) { var c = line.charCodeAt(i); if (remaining > 0) { docColumn += 1; // tab if (c == 9) { if (remaining >= tabSize) { remaining -= tabSize; } else { remaining = 0; docColumn -= 1; } } // CJK characters else if ( c >= 0x3040 && c <= 0x309F || // Hiragana c >= 0x30A0 && c <= 0x30FF || // Katakana c >= 0x4E00 && c <= 0x9FFF || // Single CJK ideographs c >= 0xF900 && c <= 0xFAFF || c >= 0x3400 && c <= 0x4DBF ) { if (remaining >= 2) { remaining -= 2; } else { remaining = 0; docColumn -= 1; } } else { remaining -= 1; } } else { break; } } // Clamp docColumn. if (this.$useWrapMode) { column = wrapData[docRow][row] if (docColumn >= column) { // We remove one character at the end such that the docColumn // position returned is not associated to the next row on the // screen. docColumn = column - 1; } } else if (line) { docColumn = Math.min(docColumn, line.length); } return { row: docRow, column: docColumn }; }; this.documentToScreenColumn = function(row, docColumn) { return this.documentToScreenPosition(row, docColumn).column; }; /** * * @return array[2] * - array[0]: The number of the row on the screen (aka screenRow) * - array[1]: The number of rows from the first docRow on the screen * (aka screenRowOffset); */ this.$documentToScreenRow = function(docRow, docColumn) { if (!this.$useWrapMode) { return [docRow, 0]; } var wrapData = this.$wrapData; var screenRow = 0; // Handle special case where the row is outside of the range of lines. if (docRow > wrapData.length - 1) { return [ this.getScreenLength(), wrapData.length == 0 ? 0 : (wrapData[wrapData.length - 1].length - 1) ]; } for (var i = 0; i < docRow; i++) { screenRow += wrapData[i].length + 1; } var screenRowOffset = 0; while (docColumn >= wrapData[docRow][screenRowOffset]) { screenRow ++; screenRowOffset++; } return [screenRow, screenRowOffset]; } this.documentToScreenRow = function(docRow, docColumn) { return this.$documentToScreenRow(docRow, docColumn)[0]; } this.documentToScreenPosition = function(pos, column) { var str; var tabSize = this.getTabSize(); // Normalize the passed in arguments. var row; if (column != null) { row = pos; } else { row = pos.row; column = pos.column; } if (!this.$useWrapMode) { str = this.getLine(row).substring(0, column); column = this.$getStringScreenWidth(str); return { row: row, column: column }; } var rowData = this.$documentToScreenRow(row, column); var screenRow = rowData[0]; if (row >= this.getLength()) { return { row: screenRow, column: 0 }; } var split; var wrapRowData = this.$wrapData[row]; var screenColumn; var screenRowOffset = rowData[1]; str = this.getLine(row).substring( wrapRowData[screenRowOffset - 1] || 0, column); screenColumn = this.$getStringScreenWidth(str); return { row: screenRow, column: screenColumn }; }; this.getScreenLength = function() { if (!this.$useWrapMode) { return this.getLength(); } var screenRows = 0; for (var row = 0; row < this.$wrapData.length; row++) { screenRows += this.$wrapData[row].length + 1; } return screenRows; } }).call(EditSession.prototype); exports.EditSession = EditSession; }); /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Ajax.org Code Editor (ACE). * * The Initial Developer of the Original Code is * Ajax.org B.V. * Portions created by the Initial Developer are Copyright (C) 2010 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Fabian Jakobs * Julian Viereck * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('ace/selection', function(require, exports, module) { var oop = require("pilot/oop"); var lang = require("pilot/lang"); var EventEmitter = require("pilot/event_emitter").EventEmitter; var Range = require("ace/range").Range; var Selection = function(session) { this.session = session; this.doc = session.getDocument(); this.clearSelection(); this.selectionLead = this.doc.createAnchor(0, 0); this.selectionAnchor = this.doc.createAnchor(0, 0); var _self = this; this.selectionLead.on("change", function(e) { _self._dispatchEvent("changeCursor"); if (!_self.$isEmpty) _self._dispatchEvent("changeSelection"); if (e.old.row == e.value.row) _self.$updateDesiredColumn(); }); this.selectionAnchor.on("change", function() { if (!_self.$isEmpty) _self._dispatchEvent("changeSelection"); }); }; (function() { oop.implement(this, EventEmitter); this.isEmpty = function() { return (this.$isEmpty || ( this.selectionAnchor.row == this.selectionLead.row && this.selectionAnchor.column == this.selectionLead.column )); }; this.isMultiLine = function() { if (this.isEmpty()) { return false; } return this.getRange().isMultiLine(); }; this.getCursor = function() { return this.selectionLead.getPosition(); }; this.setSelectionAnchor = function(row, column) { this.selectionAnchor.setPosition(row, column); if (this.$isEmpty) { this.$isEmpty = false; this._dispatchEvent("changeSelection"); } }; this.getSelectionAnchor = function() { if (this.$isEmpty) return this.getSelectionLead() else return this.selectionAnchor.getPosition(); }; this.getSelectionLead = function() { return this.selectionLead.getPosition(); }; this.shiftSelection = function(columns) { if (this.$isEmpty) { this.moveCursorTo(this.selectionLead.row, this.selectionLead.column + columns); return; }; var anchor = this.getSelectionAnchor(); var lead = this.getSelectionLead(); var isBackwards = this.isBackwards(); if (!isBackwards || anchor.column !== 0) this.setSelectionAnchor(anchor.row, anchor.column + columns); if (isBackwards || lead.column !== 0) { this.$moveSelection(function() { this.moveCursorTo(lead.row, lead.column + columns); }); } }; this.isBackwards = function() { var anchor = this.selectionAnchor; var lead = this.selectionLead; return (anchor.row > lead.row || (anchor.row == lead.row && anchor.column > lead.column)); }; this.getRange = function() { var anchor = this.selectionAnchor; var lead = this.selectionLead; if (this.isEmpty()) return Range.fromPoints(lead, lead); if (this.isBackwards()) { return Range.fromPoints(lead, anchor); } else { return Range.fromPoints(anchor, lead); } }; this.clearSelection = function() { if (!this.$isEmpty) { this.$isEmpty = true; this._dispatchEvent("changeSelection"); } }; this.selectAll = function() { var lastRow = this.doc.getLength() - 1; this.setSelectionAnchor(lastRow, this.doc.getLine(lastRow).length); this.moveCursorTo(0, 0); }; this.setSelectionRange = function(range, reverse) { if (reverse) { this.setSelectionAnchor(range.end.row, range.end.column); this.selectTo(range.start.row, range.start.column); } else { this.setSelectionAnchor(range.start.row, range.start.column); this.selectTo(range.end.row, range.end.column); } this.$updateDesiredColumn(); }; this.$updateDesiredColumn = function() { var cursor = this.getCursor(); this.$desiredColumn = this.session.documentToScreenColumn(cursor.row, cursor.column); }; this.$moveSelection = function(mover) { var lead = this.selectionLead; if (this.$isEmpty) this.setSelectionAnchor(lead.row, lead.column); mover.call(this); }; this.selectTo = function(row, column) { this.$moveSelection(function() { this.moveCursorTo(row, column); }); }; this.selectToPosition = function(pos) { this.$moveSelection(function() { this.moveCursorToPosition(pos); }); }; this.selectUp = function() { this.$moveSelection(this.moveCursorUp); }; this.selectDown = function() { this.$moveSelection(this.moveCursorDown); }; this.selectRight = function() { this.$moveSelection(this.moveCursorRight); }; this.selectLeft = function() { this.$moveSelection(this.moveCursorLeft); }; this.selectLineStart = function() { this.$moveSelection(this.moveCursorLineStart); }; this.selectLineEnd = function() { this.$moveSelection(this.moveCursorLineEnd); }; this.selectFileEnd = function() { this.$moveSelection(this.moveCursorFileEnd); }; this.selectFileStart = function() { this.$moveSelection(this.moveCursorFileStart); }; this.selectWordRight = function() { this.$moveSelection(this.moveCursorWordRight); }; this.selectWordLeft = function() { this.$moveSelection(this.moveCursorWordLeft); }; this.selectWord = function() { var cursor = this.getCursor(); var range = this.session.getWordRange(cursor.row, cursor.column); this.setSelectionRange(range); }; this.selectLine = function() { this.setSelectionAnchor(this.selectionLead.row, 0); this.$moveSelection(function() { this.moveCursorTo(this.selectionLead.row + 1, 0); }); }; this.moveCursorUp = function() { this.moveCursorBy(-1, 0); }; this.moveCursorDown = function() { this.moveCursorBy(1, 0); }; this.moveCursorLeft = function() { var cursor = this.selectionLead.getPosition(); if (cursor.column == 0) { // cursor is a line (start if (cursor.row > 0) { this.moveCursorTo(cursor.row - 1, this.doc.getLine(cursor.row - 1).length); } } else { var tabSize = this.session.getTabSize(); if (this.session.isTabStop(cursor) && this.doc.getLine(cursor.row).slice(cursor.column-tabSize, cursor.column).split(" ").length-1 == tabSize) this.moveCursorBy(0, -tabSize); else this.moveCursorBy(0, -1); } }; this.moveCursorRight = function() { if (this.selectionLead.column == this.doc.getLine(this.selectionLead.row).length) { if (this.selectionLead.row < this.doc.getLength() - 1) { this.moveCursorTo(this.selectionLead.row + 1, 0); } } else { var tabSize = this.session.getTabSize(); var cursor = this.selectionLead; if (this.session.isTabStop(cursor) && this.doc.getLine(cursor.row).slice(cursor.column, cursor.column+tabSize).split(" ").length-1 == tabSize) this.moveCursorBy(0, tabSize); else this.moveCursorBy(0, 1); } }; this.moveCursorLineStart = function() { var row = this.selectionLead.row; var column = this.selectionLead.column; var screenRow = this.session.documentToScreenRow(row, column); var firstRowColumn = this.session.getScreenFirstRowColumn(screenRow); var beforeCursor = this.doc.getLine(row).slice(firstRowColumn, column); var leadingSpace = beforeCursor.match(/^\s*/); if (leadingSpace[0].length == 0) { var lastRowColumn = this.session.getDocumentLastRowColumn(row, column); leadingSpace = this.doc.getLine(row). substring(firstRowColumn, lastRowColumn). match(/^\s*/); this.moveCursorTo(row, firstRowColumn + leadingSpace[0].length); } else if (leadingSpace[0].length >= column) { this.moveCursorTo(row, firstRowColumn); } else { this.moveCursorTo(row, firstRowColumn + leadingSpace[0].length); } }; this.moveCursorLineEnd = function() { var lead = this.selectionLead; this.moveCursorTo(lead.row, this.session.getDocumentLastRowColumn(lead.row, lead.column)); }; this.moveCursorFileEnd = function() { var row = this.doc.getLength() - 1; var column = this.doc.getLine(row).length; this.moveCursorTo(row, column); }; this.moveCursorFileStart = function() { this.moveCursorTo(0, 0); }; this.moveCursorWordRight = function() { var row = this.selectionLead.row; var column = this.selectionLead.column; var line = this.doc.getLine(row); var rightOfCursor = line.substring(column); var match; this.session.nonTokenRe.lastIndex = 0; this.session.tokenRe.lastIndex = 0; if (column == line.length) { this.moveCursorRight(); return; } else if (match = this.session.nonTokenRe.exec(rightOfCursor)) { column += this.session.nonTokenRe.lastIndex; this.session.nonTokenRe.lastIndex = 0; } else if (match = this.session.tokenRe.exec(rightOfCursor)) { column += this.session.tokenRe.lastIndex; this.session.tokenRe.lastIndex = 0; } this.moveCursorTo(row, column); }; this.moveCursorWordLeft = function() { var row = this.selectionLead.row; var column = this.selectionLead.column; var line = this.doc.getLine(row); var leftOfCursor = lang.stringReverse(line.substring(0, column)); var match; this.session.nonTokenRe.lastIndex = 0; this.session.tokenRe.lastIndex = 0; if (column == 0) { this.moveCursorLeft(); return; } else if (match = this.session.nonTokenRe.exec(leftOfCursor)) { column -= this.session.nonTokenRe.lastIndex; this.session.nonTokenRe.lastIndex = 0; } else if (match = this.session.tokenRe.exec(leftOfCursor)) { column -= this.session.tokenRe.lastIndex; this.session.tokenRe.lastIndex = 0; } this.moveCursorTo(row, column); }; this.moveCursorBy = function(rows, chars) { if (this.session.getUseWrapMode()) { var screenPos = this.session.documentToScreenPosition( this.selectionLead.row, this.selectionLead.column ); var screenCol = (chars == 0 && this.$desiredColumn) || screenPos.column; var docPos = this.session.screenToDocumentPosition(screenPos.row + rows, screenCol); this.moveCursorTo(docPos.row, docPos.column + chars, chars == 0); } else { var docColumn = (chars == 0 && this.$desiredColumn) || this.selectionLead.column; this.moveCursorTo(this.selectionLead.row + rows, docColumn + chars, chars == 0); } }; this.moveCursorToPosition = function(position) { this.moveCursorTo(position.row, position.column); }; this.moveCursorTo = function(row, column, preventUpdateDesiredColumn) { this.selectionLead.setPosition(row, column); if (!preventUpdateDesiredColumn) this.$updateDesiredColumn(this.selectionLead.column); }; this.moveCursorToScreen = function(row, column, preventUpdateDesiredColumn) { if (this.session.getUseWrapMode()) { var pos = this.session.screenToDocumentPosition(row, column); row = pos.row; column = pos.column; } this.moveCursorTo(row, column, preventUpdateDesiredColumn); }; }).call(Selection.prototype); exports.Selection = Selection; }); /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Ajax.org Code Editor (ACE). * * The Initial Developer of the Original Code is * Ajax.org B.V. * Portions created by the Initial Developer are Copyright (C) 2010 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Fabian Jakobs * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('ace/range', function(require, exports, module) { var Range = function(startRow, startColumn, endRow, endColumn) { this.start = { row: startRow, column: startColumn }; this.end = { row: endRow, column: endColumn }; }; (function() { this.toString = function() { return ("Range: [" + this.start.row + "/" + this.start.column + "] -> [" + this.end.row + "/" + this.end.column + "]"); }; this.contains = function(row, column) { return this.compare(row, column) == 0; }; this.compare = function(row, column) { if (!this.isMultiLine()) { if (row === this.start.row) { return column < this.start.column ? -1 : (column > this.end.column ? 1 : 0); }; } if (row < this.start.row) return -1; if (row > this.end.row) return 1; if (this.start.row === row) return column >= this.start.column ? 0 : -1; if (this.end.row === row) return column <= this.end.column ? 0 : 1; return 0; }; this.clipRows = function(firstRow, lastRow) { if (this.end.row > lastRow) { var end = { row: lastRow+1, column: 0 }; } if (this.start.row > lastRow) { var start = { row: lastRow+1, column: 0 }; } if (this.start.row < firstRow) { var start = { row: firstRow, column: 0 }; } if (this.end.row < firstRow) { var end = { row: firstRow, column: 0 }; } return Range.fromPoints(start || this.start, end || this.end); }; this.extend = function(row, column) { var cmp = this.compare(row, column); if (cmp == 0) return this; else if (cmp == -1) var start = {row: row, column: column}; else var end = {row: row, column: column}; return Range.fromPoints(start || this.start, end || this.end); }; this.isEmpty = function() { return (this.start.row == this.end.row && this.start.column == this.end.column); }; this.isMultiLine = function() { return (this.start.row !== this.end.row); }; this.clone = function() { return Range.fromPoints(this.start, this.end); }; this.collapseRows = function() { if (this.end.column == 0) return new Range(this.start.row, 0, Math.max(this.start.row, this.end.row-1), 0) else return new Range(this.start.row, 0, this.end.row, 0) }; this.toScreenRange = function(session) { var screenPosStart = session.documentToScreenPosition(this.start); var screenPosEnd = session.documentToScreenPosition(this.end); return new Range( screenPosStart.row, screenPosStart.column, screenPosEnd.row, screenPosEnd.column ); }; }).call(Range.prototype); Range.fromPoints = function(start, end) { return new Range(start.row, start.column, end.row, end.column); }; exports.Range = Range; }); /* vim:ts=4:sts=4:sw=4: * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Ajax.org Code Editor (ACE). * * The Initial Developer of the Original Code is * Ajax.org B.V. * Portions created by the Initial Developer are Copyright (C) 2010 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Fabian Jakobs * Mihai Sucan * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('ace/mode/text', function(require, exports, module) { var Tokenizer = require("ace/tokenizer").Tokenizer; var TextHighlightRules = require("ace/mode/text_highlight_rules").TextHighlightRules; var Mode = function() { this.$tokenizer = new Tokenizer(new TextHighlightRules().getRules()); }; (function() { this.getTokenizer = function() { return this.$tokenizer; }; this.toggleCommentLines = function(state, doc, startRow, endRow) { }; this.getNextLineIndent = function(state, line, tab) { return ""; }; this.checkOutdent = function(state, line, input) { return false; }; this.autoOutdent = function(state, doc, row) { }; this.$getIndent = function(line) { var match = line.match(/^(\s+)/); if (match) { return match[1]; } return ""; }; this.createWorker = function(session) { return null; }; this.highlightSelection = function(editor) { var session = editor.session; if (!session.$selectionOccurrences) session.$selectionOccurrences = []; if (session.$selectionOccurrences.length) this.clearSelectionHighlight(editor); var selection = editor.getSelectionRange(); if (selection.isEmpty() || selection.isMultiLine()) return; var startOuter = selection.start.column - 1; var endOuter = selection.end.column + 1; var line = session.getLine(selection.start.row); var lineCols = line.length; var needle = line.substring(Math.max(startOuter, 0), Math.min(endOuter, lineCols)); // Make sure the outer characters are not part of the word. if ((startOuter >= 0 && /^[\w\d]/.test(needle)) || (endOuter <= lineCols && /[\w\d]$/.test(needle))) return; needle = line.substring(selection.start.column, selection.end.column); if (!/^[\w\d]+$/.test(needle)) return; var cursor = editor.getCursorPosition(); var newOptions = { wrap: true, wholeWord: true, caseSensitive: true, needle: needle }; var currentOptions = editor.$search.getOptions(); editor.$search.set(newOptions); var ranges = editor.$search.findAll(session); ranges.forEach(function(range) { if (!range.contains(cursor.row, cursor.column)) { var marker = session.addMarker(range, "ace_selected_word"); session.$selectionOccurrences.push(marker); } }); editor.$search.set(currentOptions); }; this.clearSelectionHighlight = function(editor) { if (!editor.session.$selectionOccurrences) return; editor.session.$selectionOccurrences.forEach(function(marker) { editor.session.removeMarker(marker); }); editor.session.$selectionOccurrences = []; }; }).call(Mode.prototype); exports.Mode = Mode; }); /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Ajax.org Code Editor (ACE). * * The Initial Developer of the Original Code is * Ajax.org B.V. * Portions created by the Initial Developer are Copyright (C) 2010 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Fabian Jakobs * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('ace/tokenizer', function(require, exports, module) { var Tokenizer = function(rules) { this.rules = rules; this.regExps = {}; for ( var key in this.rules) { var rule = this.rules[key]; var state = rule; var ruleRegExps = []; for ( var i = 0; i < state.length; i++) ruleRegExps.push(state[i].regex); this.regExps[key] = new RegExp("(?:(" + ruleRegExps.join(")|(") + ")|(.))", "g"); } }; (function() { this.getLineTokens = function(line, startState) { var currentState = startState; var state = this.rules[currentState]; var re = this.regExps[currentState]; re.lastIndex = 0; var match, tokens = []; var lastIndex = 0; var token = { type: null, value: "" }; while (match = re.exec(line)) { var type = "text"; var value = match[0]; for ( var i = 0; i < state.length; i++) { if (match[i + 1]) { var rule = state[i]; if (typeof rule.token == "function") type = rule.token(match[0]); else type = rule.token; if (rule.next && rule.next !== currentState) { currentState = rule.next; state = this.rules[currentState]; lastIndex = re.lastIndex; re = this.regExps[currentState]; re.lastIndex = lastIndex; } break; } }; if (token.type !== type) { if (token.type) tokens.push(token); token = { type: type, value: value }; } else { token.value += value; } if (lastIndex == line.length) break; lastIndex = re.lastIndex; }; if (token.type) tokens.push(token); return { tokens : tokens, state : currentState }; }; }).call(Tokenizer.prototype); exports.Tokenizer = Tokenizer; }); /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Ajax.org Code Editor (ACE). * * The Initial Developer of the Original Code is * Ajax.org B.V. * Portions created by the Initial Developer are Copyright (C) 2010 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Fabian Jakobs * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('ace/mode/text_highlight_rules', function(require, exports, module) { var TextHighlightRules = function() { // regexp must not have capturing parentheses // regexps are ordered -> the first match is used this.$rules = { "start" : [ { token : "empty_line", regex : '^$' }, { token : "text", regex : ".+" } ] }; }; (function() { this.addRules = function(rules, prefix) { for (var key in rules) { var state = rules[key]; for (var i=0; i * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('ace/document', function(require, exports, module) { var oop = require("pilot/oop"); var EventEmitter = require("pilot/event_emitter").EventEmitter; var Range = require("ace/range").Range; var Anchor = require("ace/anchor").Anchor; var Document = function(text) { this.$lines = []; if (Array.isArray(text)) { this.insertLines(0, text); } // There has to be one line at least in the document. If you pass an empty // string to the insert function, nothing will happen. Workaround. else if (text.length == 0) { this.$lines = [""]; } else { this.insert({row: 0, column:0}, text); } }; (function() { oop.implement(this, EventEmitter); this.setValue = function(text) { var len = this.getLength(); this.remove(new Range(0, 0, len, this.getLine(len-1).length)); this.insert({row: 0, column:0}, text); }; this.getValue = function() { return this.getAllLines().join(this.getNewLineCharacter()); }; this.createAnchor = function(row, column) { return new Anchor(this, row, column); }; // check for IE split bug if ("aaa".split(/a/).length == 0) this.$split = function(text) { return text.replace(/\r\n|\r/g, "\n").split("\n"); } else this.$split = function(text) { return text.split(/\r\n|\r|\n/); }; this.$detectNewLine = function(text) { var match = text.match(/^.*?(\r?\n)/m); if (match) { this.$autoNewLine = match[1]; } else { this.$autoNewLine = "\n"; } }; this.getNewLineCharacter = function() { switch (this.$newLineMode) { case "windows": return "\r\n"; case "unix": return "\n"; case "auto": return this.$autoNewLine; } }, this.$autoNewLine = "\n"; this.$newLineMode = "auto"; this.setNewLineMode = function(newLineMode) { if (this.$newLineMode === newLineMode) return; this.$newLineMode = newLineMode; }; this.getNewLineMode = function() { return this.$newLineMode; }; this.isNewLine = function(text) { return (text == "\r\n" || text == "\r" || text == "\n"); }; /** * Get a verbatim copy of the given line as it is in the document */ this.getLine = function(row) { return this.getLines(row, row + 1)[0] || ""; }; this.getLines = function(firstRow, lastRow) { return this.$lines.slice(firstRow, lastRow + 1); }; /** * Returns all lines in the document as string array. Warning: The caller * should not modify this array! */ this.getAllLines = function() { return this.getLines(0, this.getLength()); }; this.getLength = function() { return this.$lines.length; }; this.getTextRange = function(range) { if (range.start.row == range.end.row) { return this.$lines[range.start.row].substring(range.start.column, range.end.column); } else { var lines = []; lines.push(this.$lines[range.start.row].substring(range.start.column)); lines.push.apply(lines, this.getLines(range.start.row+1, range.end.row-1)); lines.push(this.$lines[range.end.row].substring(0, range.end.column)); return lines.join(this.getNewLineCharacter()); } }; this.$clipPosition = function(position) { var length = this.getLength(); if (position.row >= length) { position.row = Math.max(0, length - 1); position.column = this.getLine(length-1).length; } return position; } this.insert = function(position, text) { if (text.length == 0) return position; position = this.$clipPosition(position); if (this.getLength() <= 1) this.$detectNewLine(text); var lines = this.$split(text); var firstLine = lines.splice(0, 1)[0]; var lastLine = lines.length == 0 ? null : lines.splice(lines.length - 1, 1)[0]; position = this.insertInLine(position, firstLine); if (lastLine !== null) { position = this.insertNewLine(position); // terminate first line position = this.insertLines(position.row, lines); position = this.insertInLine(position, lastLine || ""); } return position; }; this.insertLines = function(row, lines) { if (lines.length == 0) return {row: row, column: 0}; var args = [row, 0]; args.push.apply(args, lines); this.$lines.splice.apply(this.$lines, args); var range = new Range(row, 0, row + lines.length, 0); var delta = { action: "insertLines", range: range, lines: lines }; this._dispatchEvent("change", { data: delta }); return range.end; }, this.insertNewLine = function(position) { position = this.$clipPosition(position); var line = this.$lines[position.row] || ""; this.$lines[position.row] = line.substring(0, position.column); this.$lines.splice(position.row + 1, 0, line.substring(position.column, line.length)); var end = { row : position.row + 1, column : 0 }; var delta = { action: "insertText", range: Range.fromPoints(position, end), text: this.getNewLineCharacter() }; this._dispatchEvent("change", { data: delta }); return end; }; this.insertInLine = function(position, text) { if (text.length == 0) return position; var line = this.$lines[position.row] || ""; this.$lines[position.row] = line.substring(0, position.column) + text + line.substring(position.column); var end = { row : position.row, column : position.column + text.length }; var delta = { action: "insertText", range: Range.fromPoints(position, end), text: text }; this._dispatchEvent("change", { data: delta }); return end; }; this.remove = function(range) { // clip to document range.start = this.$clipPosition(range.start); range.end = this.$clipPosition(range.end); if (range.isEmpty()) return range.start; var firstRow = range.start.row; var lastRow = range.end.row; if (range.isMultiLine()) { var firstFullRow = range.start.column == 0 ? firstRow : firstRow + 1; var lastFullRow = lastRow - 1; if (range.end.column > 0) this.removeInLine(lastRow, 0, range.end.column); if (lastFullRow >= firstFullRow) this.removeLines(firstFullRow, lastFullRow); if (firstFullRow != firstRow) { this.removeInLine(firstRow, range.start.column, this.getLine(firstRow).length); this.removeNewLine(range.start.row); } } else { this.removeInLine(firstRow, range.start.column, range.end.column); } return range.start; }; this.removeInLine = function(row, startColumn, endColumn) { if (startColumn == endColumn) return; var range = new Range(row, startColumn, row, endColumn); var line = this.getLine(row); var removed = line.substring(startColumn, endColumn); var newLine = line.substring(0, startColumn) + line.substring(endColumn, line.length); this.$lines.splice(row, 1, newLine); var delta = { action: "removeText", range: range, text: removed }; this._dispatchEvent("change", { data: delta }); return range.start; }; /** * Removes a range of full lines * * @param firstRow {Integer} The first row to be removed * @param lastRow {Integer} The last row to be removed * @return {String[]} The removed lines */ this.removeLines = function(firstRow, lastRow) { var range = new Range(firstRow, 0, lastRow + 1, 0); var removed = this.$lines.splice(firstRow, lastRow - firstRow + 1); var delta = { action: "removeLines", range: range, nl: this.getNewLineCharacter(), lines: removed }; this._dispatchEvent("change", { data: delta }); return removed; }; this.removeNewLine = function(row) { var firstLine = this.getLine(row); var secondLine = this.getLine(row+1); var range = new Range(row, firstLine.length, row+1, 0); var line = firstLine + secondLine; this.$lines.splice(row, 2, line); var delta = { action: "removeText", range: range, text: this.getNewLineCharacter() }; this._dispatchEvent("change", { data: delta }); }; this.replace = function(range, text) { if (text.length == 0 && range.isEmpty()) return range.start; // Shortcut: If the text we want to insert is the same as it is already // in the document, we don't have to replace anything. if (text == this.getTextRange(range)) return range.end; this.remove(range); if (text) { var end = this.insert(range.start, text); } else { end = range.start; } return end; }; this.applyDeltas = function(deltas) { for (var i=0; i=0; i--) { var delta = deltas[i]; var range = Range.fromPoints(delta.range.start, delta.range.end); if (delta.action == "insertLines") this.removeLines(range.start.row, range.end.row - 1) else if (delta.action == "insertText") this.remove(range) else if (delta.action == "removeLines") this.insertLines(range.start.row, delta.lines) else if (delta.action == "removeText") this.insert(range.start, delta.text) } }; }).call(Document.prototype); exports.Document = Document; }); /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Ajax.org Code Editor (ACE). * * The Initial Developer of the Original Code is * Ajax.org B.V. * Portions created by the Initial Developer are Copyright (C) 2010 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Fabian Jakobs * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('ace/anchor', function(require, exports, module) { var oop = require("pilot/oop"); var EventEmitter = require("pilot/event_emitter").EventEmitter; /** * An Anchor is a floating pointer in the document. Whenever text is inserted or * deleted before the cursor, the position of the cursor is updated */ var Anchor = exports.Anchor = function(doc, row, column) { this.document = doc; if (typeof column == "undefined") this.setPosition(row.row, row.column); else this.setPosition(row, column); this.$onChange = this.onChange.bind(this); doc.on("change", this.$onChange); }; (function() { oop.implement(this, EventEmitter); this.getPosition = function() { return this.$clipPositionToDocument(this.row, this.column); }; this.getDocument = function() { return this.document; }; this.onChange = function(e) { var delta = e.data; var range = delta.range; if (range.start.row == range.end.row && range.start.row != this.row) return; if (range.start.row > this.row) return; if (range.start.row == this.row && range.start.column > this.column) return; var row = this.row; var column = this.column; if (delta.action === "insertText") { if (range.start.row === row && range.start.column <= column) { if (range.start.row === range.end.row) { column += range.end.column - range.start.column; } else { column -= range.start.column; row += range.end.row - range.start.row; } } else if (range.start.row !== range.end.row && range.start.row < row) { row += range.end.row - range.start.row; } } else if (delta.action === "insertLines") { if (range.start.row <= row) { row += range.end.row - range.start.row; } } else if (delta.action == "removeText") { if (range.start.row == row && range.start.column < column) { if (range.end.column >= column) column = range.start.column; else column = Math.max(0, column - (range.end.column - range.start.column)); } else if (range.start.row !== range.end.row && range.start.row < row) { if (range.end.row == row) { column = Math.max(0, column - range.end.column) + range.start.column; } row -= (range.end.row - range.start.row); } else if (range.end.row == row) { row -= range.end.row - range.start.row; column = Math.max(0, column - range.end.column) + range.start.column; } } else if (delta.action == "removeLines") { if (range.start.row <= row) { if (range.end.row <= row) row -= range.end.row - range.start.row; else { row = range.start.row; column = 0; } } } this.setPosition(row, column, true); }; this.setPosition = function(row, column, noClip) { if (noClip) { pos = { row: row, column: column }; } else { pos = this.$clipPositionToDocument(row, column); } if (this.row == pos.row && this.column == pos.column) return; var old = { row: this.row, column: this.column }; this.row = pos.row; this.column = pos.column; this._dispatchEvent("change", { old: old, value: pos }); }; this.detach = function() { this.document.removeEventListener("change", this.$onChange); }; this.$clipPositionToDocument = function(row, column) { var pos = {}; if (row >= this.document.getLength()) { pos.row = Math.max(0, this.document.getLength() - 1); pos.column = this.document.getLine(pos.row).length; } else if (row < 0) { pos.row = 0; pos.column = 0; } else { pos.row = row; pos.column = Math.min(this.document.getLine(pos.row).length, Math.max(0, column)); } if (column < 0) pos.column = 0; return pos; }; }).call(Anchor.prototype); }); /* vim:ts=4:sts=4:sw=4: * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Ajax.org Code Editor (ACE). * * The Initial Developer of the Original Code is * Ajax.org B.V. * Portions created by the Initial Developer are Copyright (C) 2010 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Fabian Jakobs * Mihai Sucan * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('ace/search', function(require, exports, module) { var lang = require("pilot/lang"); var oop = require("pilot/oop"); var Range = require("ace/range").Range; var Search = function() { this.$options = { needle: "", backwards: false, wrap: false, caseSensitive: false, wholeWord: false, scope: Search.ALL, regExp: false }; }; Search.ALL = 1; Search.SELECTION = 2; (function() { this.set = function(options) { oop.mixin(this.$options, options); return this; }; this.getOptions = function() { return lang.copyObject(this.$options); }; this.find = function(session) { if (!this.$options.needle) return null; if (this.$options.backwards) { var iterator = this.$backwardMatchIterator(session); } else { iterator = this.$forwardMatchIterator(session); } var firstRange = null; iterator.forEach(function(range) { firstRange = range; return true; }); return firstRange; }; this.findAll = function(session) { if (!this.$options.needle) return []; if (this.$options.backwards) { var iterator = this.$backwardMatchIterator(session); } else { iterator = this.$forwardMatchIterator(session); } var ranges = []; iterator.forEach(function(range) { ranges.push(range); }); return ranges; }; this.replace = function(input, replacement) { var re = this.$assembleRegExp(); var match = re.exec(input); if (match && match[0].length == input.length) { if (this.$options.regExp) { return input.replace(re, replacement); } else { return replacement; } } else { return null; } }; this.$forwardMatchIterator = function(session) { var re = this.$assembleRegExp(); var self = this; return { forEach: function(callback) { self.$forwardLineIterator(session).forEach(function(line, startIndex, row) { if (startIndex) { line = line.substring(startIndex); } var matches = []; line.replace(re, function(str) { var offset = arguments[arguments.length-2]; matches.push({ str: str, offset: startIndex + offset }); return str; }); for (var i=0; i= 0; i--) { var match = matches[i]; var range = self.$rangeFromMatch(row, match.offset, match.str.length); if (callback(range)) return true; } }); } }; }; this.$rangeFromMatch = function(row, column, length) { return new Range(row, column, row, column+length); }; this.$assembleRegExp = function() { if (this.$options.regExp) { var needle = this.$options.needle; } else { needle = lang.escapeRegExp(this.$options.needle); } if (this.$options.wholeWord) { needle = "\\b" + needle + "\\b"; } var modifier = "g"; if (!this.$options.caseSensitive) { modifier += "i"; } var re = new RegExp(needle, modifier); return re; }; this.$forwardLineIterator = function(session) { var searchSelection = this.$options.scope == Search.SELECTION; var range = session.getSelection().getRange(); var start = session.getSelection().getCursor(); var firstRow = searchSelection ? range.start.row : 0; var firstColumn = searchSelection ? range.start.column : 0; var lastRow = searchSelection ? range.end.row : session.getLength() - 1; var wrap = this.$options.wrap; var inWrap = false; function getLine(row) { var line = session.getLine(row); if (searchSelection && row == range.end.row) { line = line.substring(0, range.end.column); } if (inWrap && row == start.row) { line = line.substring(0, start.column); } return line; } return { forEach: function(callback) { var row = start.row; var line = getLine(row); var startIndex = start.column; var stop = false; inWrap = false; while (!callback(line, startIndex, row)) { if (stop) { return; } row++; startIndex = 0; if (row > lastRow) { if (wrap) { row = firstRow; startIndex = firstColumn; inWrap = true; } else { return; } } if (row == start.row) stop = true; line = getLine(row); } } }; }; this.$backwardLineIterator = function(session) { var searchSelection = this.$options.scope == Search.SELECTION; var range = session.getSelection().getRange(); var start = searchSelection ? range.end : range.start; var firstRow = searchSelection ? range.start.row : 0; var firstColumn = searchSelection ? range.start.column : 0; var lastRow = searchSelection ? range.end.row : session.getLength() - 1; var wrap = this.$options.wrap; return { forEach : function(callback) { var row = start.row; var line = session.getLine(row).substring(0, start.column); var startIndex = 0; var stop = false; var inWrap = false; while (!callback(line, startIndex, row)) { if (stop) return; row--; startIndex = 0; if (row < firstRow) { if (wrap) { row = lastRow; inWrap = true; } else { return; } } if (row == start.row) stop = true; line = session.getLine(row); if (searchSelection) { if (row == firstRow) startIndex = firstColumn; else if (row == lastRow) line = line.substring(0, range.end.column); } if (inWrap && row == start.row) startIndex = start.column; } } }; }; }).call(Search.prototype); exports.Search = Search; }); /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Ajax.org Code Editor (ACE). * * The Initial Developer of the Original Code is * Ajax.org B.V. * Portions created by the Initial Developer are Copyright (C) 2010 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Fabian Jakobs * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('ace/background_tokenizer', function(require, exports, module) { var oop = require("pilot/oop"); var EventEmitter = require("pilot/event_emitter").EventEmitter; var BackgroundTokenizer = function(tokenizer, editor) { this.running = false; this.lines = []; this.currentLine = 0; this.tokenizer = tokenizer; var self = this; this.$worker = function() { if (!self.running) { return; } var workerStart = new Date(); var startLine = self.currentLine; var doc = self.doc; var processedLines = 0; var lastVisibleRow = editor.getLastVisibleRow(); var len = doc.getLength(); while (self.currentLine < len) { self.lines[self.currentLine] = self.$tokenizeRows(self.currentLine, self.currentLine)[0]; self.currentLine++; // only check every 5 lines processedLines += 1; if ((processedLines % 5 == 0) && (new Date() - workerStart) > 20) { self.fireUpdateEvent(startLine, self.currentLine-1); var timeout = self.currentLine < lastVisibleRow ? 20 : 100; self.running = setTimeout(self.$worker, timeout); return; } } self.running = false; self.fireUpdateEvent(startLine, len - 1); }; }; (function(){ oop.implement(this, EventEmitter); this.setTokenizer = function(tokenizer) { this.tokenizer = tokenizer; this.lines = []; this.start(0); }; this.setDocument = function(doc) { this.doc = doc; this.lines = []; this.stop(); }; this.fireUpdateEvent = function(firstRow, lastRow) { var data = { first: firstRow, last: lastRow }; this._dispatchEvent("update", {data: data}); }; this.start = function(startRow) { this.currentLine = Math.min(startRow || 0, this.currentLine, this.doc.getLength()); // remove all cached items below this line this.lines.splice(this.currentLine, this.lines.length); this.stop(); // pretty long delay to prevent the tokenizer from interfering with the user this.running = setTimeout(this.$worker, 700); }; this.stop = function() { if (this.running) clearTimeout(this.running); this.running = false; }; this.getTokens = function(firstRow, lastRow) { return this.$tokenizeRows(firstRow, lastRow); }; this.getState = function(row) { return this.$tokenizeRows(row, row)[0].state; }; this.$tokenizeRows = function(firstRow, lastRow) { if (!this.doc) return []; var rows = []; // determine start state var state = "start"; var doCache = false; if (firstRow > 0 && this.lines[firstRow - 1]) { state = this.lines[firstRow - 1].state; doCache = true; } var lines = this.doc.getLines(firstRow, lastRow); for (var row=firstRow; row<=lastRow; row++) { if (!this.lines[row]) { var tokens = this.tokenizer.getLineTokens(lines[row-firstRow] || "", state); var state = tokens.state; rows.push(tokens); if (doCache) { this.lines[row] = tokens; } } else { var tokens = this.lines[row]; state = tokens.state; rows.push(tokens); } } return rows; }; }).call(BackgroundTokenizer.prototype); exports.BackgroundTokenizer = BackgroundTokenizer; }); /* vim:ts=4:sts=4:sw=4: * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Ajax.org Code Editor (ACE). * * The Initial Developer of the Original Code is * Ajax.org B.V. * Portions created by the Initial Developer are Copyright (C) 2010 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Fabian Jakobs * Mihai Sucan * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('ace/undomanager', function(require, exports, module) { var UndoManager = function() { this.reset(); }; (function() { this.execute = function(options) { var deltas = options.args[0]; this.$doc = options.args[1]; this.$undoStack.push(deltas); }; this.undo = function() { var deltas = this.$undoStack.pop(); if (deltas) { this.$doc.undoChanges(deltas); this.$redoStack.push(deltas); } }; this.redo = function() { var deltas = this.$redoStack.pop(); if (deltas) { this.$doc.redoChanges(deltas); this.$undoStack.push(deltas); } }; this.reset = function() { this.$undoStack = []; this.$redoStack = []; }; this.hasUndo = function() { return this.$undoStack.length > 0; }; this.hasRedo = function() { return this.$redoStack.length > 0; }; }).call(UndoManager.prototype); exports.UndoManager = UndoManager; }); /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Ajax.org Code Editor (ACE). * * The Initial Developer of the Original Code is * Ajax.org B.V. * Portions created by the Initial Developer are Copyright (C) 2010 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Fabian Jakobs * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('ace/theme/textmate', function(require, exports, module) { var dom = require("pilot/dom"); var cssText = ".ace-tm .ace_editor {\ border: 2px solid rgb(159, 159, 159);\ }\ \ .ace-tm .ace_editor.ace_focus {\ border: 2px solid #327fbd;\ }\ \ .ace-tm .ace_gutter {\ width: 50px;\ background: #e8e8e8;\ color: #333;\ overflow : hidden;\ }\ \ .ace-tm .ace_gutter-layer {\ width: 100%;\ text-align: right;\ }\ \ .ace-tm .ace_gutter-layer .ace_gutter-cell {\ padding-right: 6px;\ }\ \ .ace-tm .ace_print_margin {\ width: 1px;\ background: #e8e8e8;\ }\ \ .ace-tm .ace_text-layer {\ cursor: text;\ }\ \ .ace-tm .ace_cursor {\ border-left: 2px solid black;\ }\ \ .ace-tm .ace_cursor.ace_overwrite {\ border-left: 0px;\ border-bottom: 1px solid black;\ }\ \ .ace-tm .ace_line .ace_invisible {\ color: rgb(191, 191, 191);\ }\ \ .ace-tm .ace_line .ace_keyword {\ color: blue;\ }\ \ .ace-tm .ace_line .ace_constant.ace_buildin {\ color: rgb(88, 72, 246);\ }\ \ .ace-tm .ace_line .ace_constant.ace_language {\ color: rgb(88, 92, 246);\ }\ \ .ace-tm .ace_line .ace_constant.ace_library {\ color: rgb(6, 150, 14);\ }\ \ .ace-tm .ace_line .ace_invalid {\ background-color: rgb(153, 0, 0);\ color: white;\ }\ \ .ace-tm .ace_line .ace_support.ace_function {\ color: rgb(60, 76, 114);\ }\ \ .ace-tm .ace_line .ace_support.ace_constant {\ color: rgb(6, 150, 14);\ }\ \ .ace-tm .ace_line .ace_support.ace_type,\ .ace-tm .ace_line .ace_support.ace_class {\ color: rgb(109, 121, 222);\ }\ \ .ace-tm .ace_line .ace_keyword.ace_operator {\ color: rgb(104, 118, 135);\ }\ \ .ace-tm .ace_line .ace_string {\ color: rgb(3, 106, 7);\ }\ \ .ace-tm .ace_line .ace_comment {\ color: rgb(76, 136, 107);\ }\ \ .ace-tm .ace_line .ace_comment.ace_doc {\ color: rgb(0, 102, 255);\ }\ \ .ace-tm .ace_line .ace_comment.ace_doc.ace_tag {\ color: rgb(128, 159, 191);\ }\ \ .ace-tm .ace_line .ace_constant.ace_numeric {\ color: rgb(0, 0, 205);\ }\ \ .ace-tm .ace_line .ace_variable {\ color: rgb(49, 132, 149);\ }\ \ .ace-tm .ace_line .ace_xml_pe {\ color: rgb(104, 104, 91);\ }\ \ .ace-tm .ace_marker-layer .ace_selection {\ background: rgb(181, 213, 255);\ }\ \ .ace-tm .ace_marker-layer .ace_step {\ background: rgb(252, 255, 0);\ }\ \ .ace-tm .ace_marker-layer .ace_stack {\ background: rgb(164, 229, 101);\ }\ \ .ace-tm .ace_marker-layer .ace_bracket {\ margin: -1px 0 0 -1px;\ border: 1px solid rgb(192, 192, 192);\ }\ \ .ace-tm .ace_marker-layer .ace_active_line {\ background: rgb(232, 242, 254);\ }\ \ .ace-tm .ace_marker-layer .ace_selected_word {\ background: rgb(250, 250, 255);\ border: 1px solid rgb(200, 200, 250);\ }\ \ .ace-tm .ace_string.ace_regex {\ color: rgb(255, 0, 0)\ }"; // import CSS once dom.importCssString(cssText); exports.cssClass = "ace-tm"; }); /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Ajax.org Code Editor (ACE). * * The Initial Developer of the Original Code is * Ajax.org B.V. * Portions created by the Initial Developer are Copyright (C) 2010 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Fabian Jakobs * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('ace/mode/matching_brace_outdent', function(require, exports, module) { var Range = require("ace/range").Range; var MatchingBraceOutdent = function() {}; (function() { this.checkOutdent = function(line, input) { if (! /^\s+$/.test(line)) return false; return /^\s*\}/.test(input); }; this.autoOutdent = function(doc, row) { var line = doc.getLine(row); var match = line.match(/^(\s*\})/); if (!match) return 0; var column = match[1].length; var openBracePos = doc.findMatchingBracket({row: row, column: column}); if (!openBracePos || openBracePos.row == row) return 0; var indent = this.$getIndent(doc.getLine(openBracePos.row)); doc.replace(new Range(row, 0, row, column-1), indent); }; this.$getIndent = function(line) { var match = line.match(/^(\s+)/); if (match) { return match[1]; } return ""; }; }).call(MatchingBraceOutdent.prototype); exports.MatchingBraceOutdent = MatchingBraceOutdent; }); /* vim:ts=4:sts=4:sw=4: * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Ajax.org Code Editor (ACE). * * The Initial Developer of the Original Code is * Ajax.org B.V. * Portions created by the Initial Developer are Copyright (C) 2010 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Fabian Jakobs * Irakli Gozalishvili (http://jeditoolkit.com) * Julian Viereck * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('ace/virtual_renderer', function(require, exports, module) { var oop = require("pilot/oop"); var dom = require("pilot/dom"); var event = require("pilot/event"); var useragent = require("pilot/useragent"); var GutterLayer = require("ace/layer/gutter").Gutter; var MarkerLayer = require("ace/layer/marker").Marker; var TextLayer = require("ace/layer/text").Text; var CursorLayer = require("ace/layer/cursor").Cursor; var ScrollBar = require("ace/scrollbar").ScrollBar; var RenderLoop = require("ace/renderloop").RenderLoop; var EventEmitter = require("pilot/event_emitter").EventEmitter; var editorCss = require("text/ace/css/editor.css"); // import CSS once dom.importCssString(editorCss); var VirtualRenderer = function(container, theme) { this.container = container; dom.addCssClass(this.container, "ace_editor"); this.setTheme(theme); this.$gutter = dom.createElement("div"); this.$gutter.className = "ace_gutter"; this.container.appendChild(this.$gutter); this.scroller = dom.createElement("div"); this.scroller.className = "ace_scroller"; this.container.appendChild(this.scroller); this.content = dom.createElement("div"); this.content.className = "ace_content"; this.scroller.appendChild(this.content); this.$gutterLayer = new GutterLayer(this.$gutter); this.$markerBack = new MarkerLayer(this.content); var textLayer = this.$textLayer = new TextLayer(this.content); this.canvas = textLayer.element; this.$markerFront = new MarkerLayer(this.content); this.characterWidth = textLayer.getCharacterWidth(); this.lineHeight = textLayer.getLineHeight(); this.$cursorLayer = new CursorLayer(this.content); this.$cursorPadding = 8; // Indicates whether the horizontal scrollbar is visible this.$horizScroll = true; this.$horizScrollAlwaysVisible = true; this.scrollBar = new ScrollBar(container); this.scrollBar.addEventListener("scroll", this.onScroll.bind(this)); this.scrollTop = 0; this.cursorPos = { row : 0, column : 0 }; var _self = this; this.$textLayer.addEventListener("changeCharaterSize", function() { _self.characterWidth = textLayer.getCharacterWidth(); _self.lineHeight = textLayer.getLineHeight(); _self.$updatePrintMargin(); _self.onResize(true); _self.$loop.schedule(_self.CHANGE_FULL); }); event.addListener(this.$gutter, "click", this.$onGutterClick.bind(this)); event.addListener(this.$gutter, "dblclick", this.$onGutterClick.bind(this)); this.$size = { width: 0, height: 0, scrollerHeight: 0, scrollerWidth: 0 }; this.$loop = new RenderLoop(this.$renderChanges.bind(this)); this.$loop.schedule(this.CHANGE_FULL); this.setPadding(4); this.$updatePrintMargin(); }; (function() { this.showGutter = true; this.CHANGE_CURSOR = 1; this.CHANGE_MARKER = 2; this.CHANGE_GUTTER = 4; this.CHANGE_SCROLL = 8; this.CHANGE_LINES = 16; this.CHANGE_TEXT = 32; this.CHANGE_SIZE = 64; this.CHANGE_MARKER_BACK = 128; this.CHANGE_MARKER_FRONT = 256; this.CHANGE_FULL = 512; oop.implement(this, EventEmitter); this.setSession = function(session) { this.session = session; this.$cursorLayer.setSession(session); this.$markerBack.setSession(session); this.$markerFront.setSession(session); this.$gutterLayer.setSession(session); this.$textLayer.setSession(session); this.$loop.schedule(this.CHANGE_FULL); }; /** * Triggers partial update of the text layer */ this.updateLines = function(firstRow, lastRow) { if (lastRow === undefined) lastRow = Infinity; if (!this.$changedLines) { this.$changedLines = { firstRow: firstRow, lastRow: lastRow }; } else { if (this.$changedLines.firstRow > firstRow) this.$changedLines.firstRow = firstRow; if (this.$changedLines.lastRow < lastRow) this.$changedLines.lastRow = lastRow; } this.$loop.schedule(this.CHANGE_LINES); }; /** * Triggers full update of the text layer */ this.updateText = function() { this.$loop.schedule(this.CHANGE_TEXT); }; /** * Triggers a full update of all layers */ this.updateFull = function() { this.$loop.schedule(this.CHANGE_FULL); }; this.updateFontSize = function() { this.$textLayer.checkForSizeChanges(); }; /** * Triggers resize of the editor */ this.onResize = function(force) { var changes = this.CHANGE_SIZE; var height = dom.getInnerHeight(this.container); if (force || this.$size.height != height) { this.$size.height = height; this.scroller.style.height = height + "px"; this.scrollBar.setHeight(this.scroller.clientHeight); if (this.session) { this.scrollToY(this.getScrollTop()); changes = changes | this.CHANGE_FULL; } } var width = dom.getInnerWidth(this.container); if (force || this.$size.width != width) { this.$size.width = width; var gutterWidth = this.showGutter ? this.$gutter.offsetWidth : 0; this.scroller.style.left = gutterWidth + "px"; this.scroller.style.width = Math.max(0, width - gutterWidth - this.scrollBar.getWidth()) + "px"; if (this.session.getUseWrapMode()) { var availableWidth = this.scroller.clientWidth - this.$padding * 2; var limit = Math.floor(availableWidth / this.characterWidth) - 1; if (this.session.adjustWrapLimit(limit) || force) { changes = changes | this.CHANGE_FULL; } } } this.$size.scrollerWidth = this.scroller.clientWidth; this.$size.scrollerHeight = this.scroller.clientHeight; this.$loop.schedule(changes); }; this.setTokenizer = function(tokenizer) { this.$tokenizer = tokenizer; this.$textLayer.setTokenizer(tokenizer); this.$loop.schedule(this.CHANGE_TEXT); }; this.$onGutterClick = function(e) { var pageX = event.getDocumentX(e); var pageY = event.getDocumentY(e); this._dispatchEvent("gutter" + e.type, { row: this.screenToTextCoordinates(pageX, pageY).row, htmlEvent: e }); }; this.setShowInvisibles = function(showInvisibles) { if (this.$textLayer.setShowInvisibles(showInvisibles)) this.$loop.schedule(this.CHANGE_TEXT); }; this.getShowInvisibles = function() { return this.$textLayer.showInvisibles; }; this.$showPrintMargin = true; this.setShowPrintMargin = function(showPrintMargin) { this.$showPrintMargin = showPrintMargin; this.$updatePrintMargin(); }; this.getShowPrintMargin = function() { return this.$showPrintMargin; }; this.$printMarginColumn = 80; this.setPrintMarginColumn = function(showPrintMargin) { this.$printMarginColumn = showPrintMargin; this.$updatePrintMargin(); }; this.getPrintMarginColumn = function() { return this.$printMarginColumn; }; this.getShowGutter = function(){ return this.showGutter; } this.setShowGutter = function(show){ if(this.showGutter === show) return; this.$gutter.style.display = show ? "block" : "none"; this.showGutter = show; this.onResize(true); } this.$updatePrintMargin = function() { var containerEl if (!this.$showPrintMargin && !this.$printMarginEl) return; if (!this.$printMarginEl) { containerEl = dom.createElement("div"); containerEl.className = "ace_print_margin_layer"; this.$printMarginEl = dom.createElement("div") this.$printMarginEl.className = "ace_print_margin"; containerEl.appendChild(this.$printMarginEl); this.content.insertBefore(containerEl, this.$textLayer.element); } var style = this.$printMarginEl.style; style.left = ((this.characterWidth * this.$printMarginColumn) + this.$padding * 2) + "px"; style.visibility = this.$showPrintMargin ? "visible" : "hidden"; }; this.getContainerElement = function() { return this.container; }; this.getMouseEventTarget = function() { return this.content; }; this.getTextAreaContainer = function() { return this.container; }; this.moveTextAreaToCursor = function(textarea) { // in IE the native cursor always shines through if (useragent.isIE) return; var pos = this.$cursorLayer.getPixelPosition(); if (!pos) return; var bounds = this.content.getBoundingClientRect(); var offset = (this.layerConfig && this.layerConfig.offset) || 0; textarea.style.left = (bounds.left + pos.left + this.$padding) + "px"; textarea.style.top = (bounds.top + pos.top - this.scrollTop + offset) + "px"; }; this.getFirstVisibleRow = function() { return (this.layerConfig || {}).firstRow || 0; }; this.getFirstFullyVisibleRow = function(){ if (!this.layerConfig) return 0; return this.layerConfig.firstRow + (this.layerConfig.offset == 0 ? 0 : 1); } this.getLastFullyVisibleRow = function() { if (!this.layerConfig) return 0; var flint = Math.floor((this.layerConfig.height + this.layerConfig.offset) / this.layerConfig.lineHeight); return this.layerConfig.firstRow - 1 + flint; } this.getLastVisibleRow = function() { return (this.layerConfig || {}).lastRow || 0; }; this.$padding = null; this.setPadding = function(padding) { this.$padding = padding; this.content.style.padding = "0 " + padding + "px"; this.$loop.schedule(this.CHANGE_FULL); this.$updatePrintMargin(); }; this.getHScrollBarAlwaysVisible = function() { return this.$horizScrollAlwaysVisible; } this.setHScrollBarAlwaysVisible = function(alwaysVisible) { if (this.$horizScrollAlwaysVisible != alwaysVisible) { this.$horizScrollAlwaysVisible = alwaysVisible; if (!this.$horizScrollAlwaysVisible || !this.$horizScroll) this.$loop.schedule(this.CHANGE_SCROLL); } } this.onScroll = function(e) { this.scrollToY(e.data); }; this.$updateScrollBar = function() { this.scrollBar.setInnerHeight(this.session.getScreenLength() * this.lineHeight); this.scrollBar.setScrollTop(this.scrollTop); }; this.$renderChanges = function(changes) { if (!changes || !this.session || !this.$tokenizer) return; // text, scrolling and resize changes can cause the view port size to change if (!this.layerConfig || changes & this.CHANGE_FULL || changes & this.CHANGE_SIZE || changes & this.CHANGE_TEXT || changes & this.CHANGE_LINES || changes & this.CHANGE_SCROLL ) this.$computeLayerConfig(); // full if (changes & this.CHANGE_FULL) { this.$textLayer.update(this.layerConfig); this.showGutter && this.$gutterLayer.update(this.layerConfig); this.$markerBack.update(this.layerConfig); this.$markerFront.update(this.layerConfig); this.$cursorLayer.update(this.layerConfig); this.$updateScrollBar(); this.scrollCursorIntoView(); return; } // scrolling if (changes & this.CHANGE_SCROLL) { if (changes & this.CHANGE_TEXT || changes & this.CHANGE_LINES) this.$textLayer.update(this.layerConfig); else this.$textLayer.scrollLines(this.layerConfig); this.showGutter && this.$gutterLayer.update(this.layerConfig); this.$markerBack.update(this.layerConfig); this.$markerFront.update(this.layerConfig); this.$cursorLayer.update(this.layerConfig); this.$updateScrollBar(); return; } if (changes & this.CHANGE_TEXT) { this.$textLayer.update(this.layerConfig); this.showGutter && this.$gutterLayer.update(this.layerConfig); } else if (changes & this.CHANGE_LINES) { this.$updateLines(); this.$updateScrollBar(); this.showGutter && this.$gutterLayer.update(this.layerConfig); } else if (changes & this.CHANGE_GUTTER) { this.showGutter && this.$gutterLayer.update(this.layerConfig); } if (changes & this.CHANGE_CURSOR) this.$cursorLayer.update(this.layerConfig); if (changes & (this.CHANGE_MARKER | this.CHANGE_MARKER_FRONT)) { this.$markerFront.update(this.layerConfig); } if (changes & (this.CHANGE_MARKER | this.CHANGE_MARKER_BACK)) { this.$markerBack.update(this.layerConfig); } if (changes & this.CHANGE_SIZE) this.$updateScrollBar(); }; this.$computeLayerConfig = function() { var session = this.session; var offset = this.scrollTop % this.lineHeight; var minHeight = this.$size.scrollerHeight + this.lineHeight; var longestLine = this.$getLongestLine(); var widthChanged = !this.layerConfig ? true : (this.layerConfig.width != longestLine); var horizScroll = this.$horizScrollAlwaysVisible || this.$size.scrollerWidth - longestLine < 0; var horizScrollChanged = this.$horizScroll !== horizScroll; this.$horizScroll = horizScroll; if (horizScrollChanged) this.scroller.style.overflowX = horizScroll ? "scroll" : "hidden"; var lineCount = Math.ceil(minHeight / this.lineHeight) - 1; var firstRow = Math.max(0, Math.round((this.scrollTop - offset) / this.lineHeight)); var lastRow = firstRow + lineCount; // Map lines on the screen to lines in the document. var firstRowScreen, firstRowHeight; var lineHeight = { lineHeight: this.lineHeight }; firstRow = session.screenToDocumentRow(firstRow); firstRowScreen = session.documentToScreenRow(firstRow); firstRowHeight = session.getRowHeight(lineHeight, firstRow); lastRow = Math.min(session.screenToDocumentRow(lastRow), session.getLength() - 1); minHeight = this.$size.scrollerHeight + session.getRowHeight(lineHeight, lastRow)+ firstRowHeight; offset = this.scrollTop - firstRowScreen * this.lineHeight; var layerConfig = this.layerConfig = { width : longestLine, padding : this.$padding, firstRow : firstRow, firstRowScreen: firstRowScreen, lastRow : lastRow, lineHeight : this.lineHeight, characterWidth : this.characterWidth, minHeight : minHeight, offset : offset, height : this.$size.scrollerHeight }; this.$gutterLayer.element.style.marginTop = (-offset) + "px"; this.content.style.marginTop = (-offset) + "px"; this.content.style.width = longestLine + "px"; this.content.style.height = minHeight + "px"; // Horizontal scrollbar visibility may have changed, which changes // the client height of the scroller if (horizScrollChanged) this.onResize(true); }; this.$updateLines = function() { var firstRow = this.$changedLines.firstRow; var lastRow = this.$changedLines.lastRow; this.$changedLines = null; var layerConfig = this.layerConfig; // if the update changes the width of the document do a full redraw if (layerConfig.width != this.$getLongestLine()) return this.$textLayer.update(layerConfig); if (firstRow > layerConfig.lastRow + 1) { return; } if (lastRow < layerConfig.firstRow) { return; } // if the last row is unknown -> redraw everything if (lastRow === Infinity) { this.showGutter && this.$gutterLayer.update(layerConfig); this.$textLayer.update(layerConfig); return; } // else update only the changed rows this.$textLayer.updateLines(layerConfig, firstRow, lastRow); }; this.$getLongestLine = function() { var charCount = this.session.getScreenWidth() + 1; if (this.$textLayer.showInvisibles) charCount += 1; return Math.max(this.$size.scrollerWidth - this.$padding * 2, Math.round(charCount * this.characterWidth)); }; this.updateFrontMarkers = function() { this.$markerFront.setMarkers(this.session.getMarkers(true)); this.$loop.schedule(this.CHANGE_MARKER_FRONT); }; this.updateBackMarkers = function() { this.$markerBack.setMarkers(this.session.getMarkers()); this.$loop.schedule(this.CHANGE_MARKER_BACK); }; this.addGutterDecoration = function(row, className){ this.$gutterLayer.addGutterDecoration(row, className); this.$loop.schedule(this.CHANGE_GUTTER); } this.removeGutterDecoration = function(row, className){ this.$gutterLayer.removeGutterDecoration(row, className); this.$loop.schedule(this.CHANGE_GUTTER); } this.setBreakpoints = function(rows) { this.$gutterLayer.setBreakpoints(rows); this.$loop.schedule(this.CHANGE_GUTTER); }; this.setAnnotations = function(annotations) { this.$gutterLayer.setAnnotations(annotations); this.$loop.schedule(this.CHANGE_GUTTER); }; this.updateCursor = function() { this.$loop.schedule(this.CHANGE_CURSOR); }; this.hideCursor = function() { this.$cursorLayer.hideCursor(); }; this.showCursor = function() { this.$cursorLayer.showCursor(); }; this.scrollCursorIntoView = function() { // the editor is not visible if (this.$size.scrollerHeight === 0) return; var pos = this.$cursorLayer.getPixelPosition(); var left = pos.left + this.$padding; var top = pos.top; if (this.getScrollTop() > top) { this.scrollToY(top); } if (this.getScrollTop() + this.$size.scrollerHeight < top + this.lineHeight) { this.scrollToY(top + this.lineHeight - this.$size.scrollerHeight); } if (this.scroller.scrollLeft > left) { this.scrollToX(left); } if (this.scroller.scrollLeft + this.$size.scrollerWidth < left + this.characterWidth) { if (left + this.characterWidth > this.scroller.scrollWidth) this.$renderChanges(this.CHANGE_SIZE); this.scrollToX(Math.round(left + this.characterWidth - this.$size.scrollerWidth)); } }, this.getScrollTop = function() { return this.scrollTop; }; this.getScrollLeft = function() { return this.scroller.scrollLeft; }; this.getScrollTopRow = function() { return this.scrollTop / this.lineHeight; }; this.getScrollBottomRow = function() { return Math.max(0, Math.floor((this.scrollTop + this.$size.scrollerHeight) / this.lineHeight) - 1); } this.scrollToRow = function(row) { this.scrollToY(row * this.lineHeight); }; this.scrollToLine = function(line, center) { var lineHeight = { lineHeight: this.lineHeight }; var offset = 0; for (var l = 1; l < line; l++) { offset += this.session.getRowHeight(lineHeight, l-1); } if (center) { offset -= this.$size.scrollerHeight / 2; } this.scrollToY(offset); }; this.scrollToY = function(scrollTop) { var maxHeight = this.session.getScreenLength() * this.lineHeight - this.$size.scrollerHeight; var scrollTop = Math.max(0, Math.min(maxHeight, scrollTop)); if (this.scrollTop !== scrollTop) { this.scrollTop = scrollTop; this.$loop.schedule(this.CHANGE_SCROLL); } }; this.scrollToX = function(scrollLeft) { if (scrollLeft <= this.$padding) scrollLeft = 0; this.scroller.scrollLeft = scrollLeft; }; this.scrollBy = function(deltaX, deltaY) { deltaY && this.scrollToY(this.scrollTop + deltaY); deltaX && this.scrollToX(this.scroller.scrollLeft + deltaX); }; this.screenToTextCoordinates = function(pageX, pageY) { var canvasPos = this.scroller.getBoundingClientRect(); var col = Math.round((pageX + this.scroller.scrollLeft - canvasPos.left - this.$padding - dom.getPageScrollLeft()) / this.characterWidth); var row = Math.floor((pageY + this.scrollTop - canvasPos.top - dom.getPageScrollTop()) / this.lineHeight); return this.session.screenToDocumentPosition(row, Math.max(col, 0)); }; this.textToScreenCoordinates = function(row, column) { var canvasPos = this.scroller.getBoundingClientRect(); var pos = this.session.documentToScreenPosition(row, column); var x = this.$padding + Math.round(pos.column * this.characterWidth); var y = pos.row * this.lineHeight; return { pageX: canvasPos.left + x - this.getScrollLeft(), pageY: canvasPos.top + y - this.getScrollTop() } }; this.visualizeFocus = function() { dom.addCssClass(this.container, "ace_focus"); }; this.visualizeBlur = function() { dom.removeCssClass(this.container, "ace_focus"); }; this.showComposition = function(position) { if (!this.$composition) { this.$composition = dom.createElement("div"); this.$composition.className = "ace_composition"; this.content.appendChild(this.$composition); } this.$composition.innerHTML = " "; var pos = this.$cursorLayer.getPixelPosition(); var style = this.$composition.style; style.top = pos.top + "px"; style.left = (pos.left + this.$padding) + "px"; style.height = this.lineHeight + "px"; this.hideCursor(); }; this.setCompositionText = function(text) { dom.setInnerText(this.$composition, text); }; this.hideComposition = function() { this.showCursor(); if (!this.$composition) return; var style = this.$composition.style; style.top = "-10000px"; style.left = "-10000px"; }; this.setTheme = function(theme) { var _self = this; if (!theme || typeof theme == "string") { theme = theme || "ace/theme/textmate"; require([theme], function(theme) { afterLoad(theme); }); } else { afterLoad(theme); } var _self = this; function afterLoad(theme) { if (_self.$theme) dom.removeCssClass(_self.container, _self.$theme); _self.$theme = theme ? theme.cssClass : null; if (_self.$theme) dom.addCssClass(_self.container, _self.$theme); // force re-measure of the gutter width if (_self.$size) { _self.$size.width = 0; _self.onResize(); } } }; // Methods allows to add / remove CSS classnames to the editor element. // This feature can be used by plug-ins to provide a visual indication of // a certain mode that editor is in. this.setStyle = function setStyle(style) { dom.addCssClass(this.container, style) }; this.unsetStyle = function unsetStyle(style) { dom.removeCssClass(this.container, style) }; }).call(VirtualRenderer.prototype); exports.VirtualRenderer = VirtualRenderer; }); /* vim:ts=4:sts=4:sw=4: * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Ajax.org Code Editor (ACE). * * The Initial Developer of the Original Code is * Ajax.org B.V. * Portions created by the Initial Developer are Copyright (C) 2010 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Fabian Jakobs * Julian Viereck * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('ace/layer/gutter', function(require, exports, module) { var dom = require("pilot/dom"); var Gutter = function(parentEl) { this.element = dom.createElement("div"); this.element.className = "ace_layer ace_gutter-layer"; parentEl.appendChild(this.element); this.$breakpoints = []; this.$annotations = []; this.$decorations = []; }; (function() { this.setSession = function(session) { this.session = session; }; this.addGutterDecoration = function(row, className){ if (!this.$decorations[row]) this.$decorations[row] = ""; this.$decorations[row] += " ace_" + className; } this.removeGutterDecoration = function(row, className){ this.$decorations[row] = this.$decorations[row].replace(" ace_" + className, ""); }; this.setBreakpoints = function(rows) { this.$breakpoints = rows.concat(); }; this.setAnnotations = function(annotations) { // iterate over sparse array this.$annotations = []; for (var row in annotations) if (annotations.hasOwnProperty(row)) { var rowAnnotations = annotations[row]; if (!rowAnnotations) continue; var rowInfo = this.$annotations[row] = { text: [] }; for (var i=0; i", (i+1), ""); } this.element = dom.setInnerHtml(this.element, html.join("")); this.element.style.height = config.minHeight + "px"; }; }).call(Gutter.prototype); exports.Gutter = Gutter; }); /* vim:ts=4:sts=4:sw=4: * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Ajax.org Code Editor (ACE). * * The Initial Developer of the Original Code is * Ajax.org B.V. * Portions created by the Initial Developer are Copyright (C) 2010 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Fabian Jakobs * Julian Viereck * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('ace/layer/marker', function(require, exports, module) { var Range = require("ace/range").Range; var dom = require("pilot/dom"); var Marker = function(parentEl) { this.element = dom.createElement("div"); this.element.className = "ace_layer ace_marker-layer"; parentEl.appendChild(this.element); }; (function() { this.setSession = function(session) { this.session = session; }; this.setMarkers = function(markers) { this.markers = markers; }; this.update = function(config) { var config = config || this.config; if (!config) return; this.config = config; var html = []; for ( var key in this.markers) { var marker = this.markers[key]; var range = marker.range.clipRows(config.firstRow, config.lastRow); if (range.isEmpty()) continue; range = range.toScreenRange(this.session); if (marker.renderer) { var top = this.$getTop(range.start.row, config); var left = Math.round(range.start.column * config.characterWidth); marker.renderer(html, range, left, top, config); } else if (range.isMultiLine()) { if (marker.type == "text") { this.drawTextMarker(html, range, marker.clazz, config); } else { this.drawMultiLineMarker(html, range, marker.clazz, config); } } else { this.drawSingleLineMarker(html, range, marker.clazz, config); } } this.element = dom.setInnerHtml(this.element, html.join("")); }; this.$getTop = function(row, layerConfig) { return (row - layerConfig.firstRowScreen) * layerConfig.lineHeight; }; this.drawTextMarker = function(stringBuilder, range, clazz, layerConfig) { // selection start var row = range.start.row; var lineRange = new Range(row, range.start.column, row, this.session.getScreenLastRowColumn(row)); this.drawSingleLineMarker(stringBuilder, lineRange, clazz, layerConfig, 1); // selection end var row = range.end.row; var lineRange = new Range(row, 0, row, range.end.column); this.drawSingleLineMarker(stringBuilder, lineRange, clazz, layerConfig); for (var row = range.start.row + 1; row < range.end.row; row++) { lineRange.start.row = row; lineRange.end.row = row; lineRange.end.column = this.session.getScreenLastRowColumn(row); this.drawSingleLineMarker(stringBuilder, lineRange, clazz, layerConfig, 1); } }; this.drawMultiLineMarker = function(stringBuilder, range, clazz, layerConfig) { // from selection start to the end of the line var height = layerConfig.lineHeight; var width = Math.round(layerConfig.width - (range.start.column * layerConfig.characterWidth)); var top = this.$getTop(range.start.row, layerConfig); var left = Math.round(range.start.column * layerConfig.characterWidth); stringBuilder.push( "
" ); // from start of the last line to the selection end var top = this.$getTop(range.end.row, layerConfig); var width = Math.round(range.end.column * layerConfig.characterWidth); stringBuilder.push( "
" ); // all the complete lines var height = (range.end.row - range.start.row - 1) * layerConfig.lineHeight; if (height < 0) return; var top = this.$getTop(range.start.row + 1, layerConfig); stringBuilder.push( "
" ); }; this.drawSingleLineMarker = function(stringBuilder, range, clazz, layerConfig, extraLength) { var height = layerConfig.lineHeight; var width = Math.round((range.end.column + (extraLength || 0) - range.start.column) * layerConfig.characterWidth); var top = this.$getTop(range.start.row, layerConfig); var left = Math.round(range.start.column * layerConfig.characterWidth); stringBuilder.push( "
" ); }; }).call(Marker.prototype); exports.Marker = Marker; }); /* vim:ts=4:sts=4:sw=4: * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Ajax.org Code Editor (ACE). * * The Initial Developer of the Original Code is * Ajax.org B.V. * Portions created by the Initial Developer are Copyright (C) 2010 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Fabian Jakobs * Julian Viereck * Mihai Sucan * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('ace/layer/text', function(require, exports, module) { var oop = require("pilot/oop"); var dom = require("pilot/dom"); var lang = require("pilot/lang"); var EventEmitter = require("pilot/event_emitter").EventEmitter; var Text = function(parentEl) { this.element = dom.createElement("div"); this.element.className = "ace_layer ace_text-layer"; parentEl.appendChild(this.element); this.$characterSize = this.$measureSizes() || {width: 0, height: 0}; this.$pollSizeChanges(); }; (function() { oop.implement(this, EventEmitter); this.EOF_CHAR = "¶"; this.EOL_CHAR = "¬"; this.TAB_CHAR = "→"; this.SPACE_CHAR = "·"; this.setTokenizer = function(tokenizer) { this.tokenizer = tokenizer; }; this.getLineHeight = function() { return this.$characterSize.height || 1; }; this.getCharacterWidth = function() { return this.$characterSize.width || 1; }; this.checkForSizeChanges = function() { var size = this.$measureSizes(); if (size && (this.$characterSize.width !== size.width || this.$characterSize.height !== size.height)) { this.$characterSize = size; this._dispatchEvent("changeCharaterSize", {data: size}); } }; this.$pollSizeChanges = function() { var self = this; setInterval(function() { self.checkForSizeChanges(); }, 500); }; this.$fontStyles = { fontFamily : 1, fontSize : 1, fontWeight : 1, fontStyle : 1, lineHeight : 1 }; this.$measureSizes = function() { var n = 1000; if (!this.$measureNode) { var measureNode = this.$measureNode = dom.createElement("div"); var style = measureNode.style; style.width = style.height = "auto"; style.left = style.top = (-n * 40) + "px"; style.visibility = "hidden"; style.position = "absolute"; style.overflow = "visible"; style.whiteSpace = "nowrap"; // in FF 3.6 monospace fonts can have a fixed sub pixel width. // that's why we have to measure many characters // Note: characterWidth can be a float! measureNode.innerHTML = lang.stringRepeat("Xy", n); if (document.body) { document.body.appendChild(measureNode); } else { var container = this.element.parentNode; while (!dom.hasCssClass(container, "ace_editor")) container = container.parentNode; container.appendChild(measureNode); } } var style = this.$measureNode.style; for (var prop in this.$fontStyles) { var value = dom.computedStyle(this.element, prop); style[prop] = value; } var size = { height: this.$measureNode.offsetHeight, width: this.$measureNode.offsetWidth / (n * 2) }; // Size and width can be null if the editor is not visible or // detached from the document if (size.width == 0 && size.height == 0) return null; return size; }; this.setSession = function(session) { this.session = session; }; this.showInvisibles = false; this.setShowInvisibles = function(showInvisibles) { if (this.showInvisibles == showInvisibles) return false; this.showInvisibles = showInvisibles; return true; }; this.$computeTabString = function() { var tabSize = this.session.getTabSize(); if (this.showInvisibles) { var halfTab = (tabSize) / 2; this.$tabString = "" + new Array(Math.floor(halfTab)).join(" ") + this.TAB_CHAR + new Array(Math.ceil(halfTab)+1).join(" ") + ""; } else { this.$tabString = new Array(tabSize+1).join(" "); } }; this.updateLines = function(config, firstRow, lastRow) { this.$computeTabString(); // Due to wrap line changes there can be new lines if e.g. // the line to updated wrapped in the meantime. if (this.config.lastRow != config.lastRow || this.config.firstRow != config.firstRow) { this.scrollLines(config); } this.config = config; var first = Math.max(firstRow, config.firstRow); var last = Math.min(lastRow, config.lastRow); var lineElements = this.element.childNodes; var tokens = this.tokenizer.getTokens(first, last); for (var i=first; i<=last; i++) { var lineElement = lineElements[i - config.firstRow]; if (!lineElement) continue; var html = []; this.$renderLine(html, i, tokens[i-first].tokens); lineElement = dom.setInnerHtml(lineElement, html.join("")); lineElement.style.height = this.session.getRowHeight(config, i) + "px"; } }; this.scrollLines = function(config) { this.$computeTabString(); var oldConfig = this.config; this.config = config; if (!oldConfig || oldConfig.lastRow < config.firstRow) return this.update(config); if (config.lastRow < oldConfig.firstRow) return this.update(config); var el = this.element; if (oldConfig.firstRow < config.firstRow) for (var row=oldConfig.firstRow; row config.lastRow) for (var row=config.lastRow+1; row<=oldConfig.lastRow; row++) el.removeChild(el.lastChild); if (config.firstRow < oldConfig.firstRow) { var fragment = this.$renderLinesFragment(config, config.firstRow, oldConfig.firstRow - 1); if (el.firstChild) el.insertBefore(fragment, el.firstChild); else el.appendChild(fragment); } if (config.lastRow > oldConfig.lastRow) { var fragment = this.$renderLinesFragment(config, oldConfig.lastRow + 1, config.lastRow); el.appendChild(fragment); } }; this.$renderLinesFragment = function(config, firstRow, lastRow) { var fragment = document.createDocumentFragment(); var tokens = this.tokenizer.getTokens(firstRow, lastRow); for (var row=firstRow; row<=lastRow; row++) { var lineEl = dom.createElement("div"); lineEl.className = "ace_line"; var style = lineEl.style; style.height = this.session.getRowHeight(config, row) + "px"; style.width = config.width + "px"; var html = []; if (tokens.length > row-firstRow) this.$renderLine(html, row, tokens[row-firstRow].tokens); // don't use setInnerHtml since we are working with an empty DIV lineEl.innerHTML = html.join(""); fragment.appendChild(lineEl); } return fragment; }; this.update = function(config) { this.$computeTabString(); this.config = config; var html = []; var tokens = this.tokenizer.getTokens(config.firstRow, config.lastRow) var fragment = this.$renderLinesFragment(config, config.firstRow, config.lastRow); // Clear the current content of the element and add the rendered fragment. this.element.innerHTML = ""; this.element.appendChild(fragment); }; this.$textToken = { "text": true, "rparen": true, "lparen": true }; this.$renderLine = function(stringBuilder, row, tokens) { if (this.showInvisibles) { var self = this; var spaceRe = /( +)|([\v\f \u00a0\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u3000])/g; var spaceReplace = function(space) { if (space.charCodeAt(0) == 32) return new Array(space.length+1).join(" "); else { var space = new Array(space.length+1).join(self.SPACE_CHAR); return "" + space + ""; } }; } else { var spaceRe = /[\v\f \u00a0\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u3000]/g; var spaceReplace = " "; } var _self = this; var characterWidth = this.config.characterWidth; function addToken(token, value) { var output = value .replace(/&/g, "&") .replace(/" + c + "" }); if (!_self.$textToken[token.type]) { var classes = "ace_" + token.type.replace(/\./g, " ace_"); stringBuilder.push("", output, ""); } else { stringBuilder.push(output); } } var splits = this.session.getRowSplitData(row); var chars = 0, split = 0, splitChars; if (!splits || splits.length == 0) { splitChars = Number.MAX_VALUE; } else { splitChars = splits[0]; } stringBuilder.push("
"); for (var i = 0; i < tokens.length; i++) { var token = tokens[i]; var value = token.value; if (chars + value.length < splitChars) { addToken(token, value); chars += value.length; } else { while (chars + value.length >= splitChars) { addToken(token, value.substring(0, splitChars - chars)); value = value.substring(splitChars - chars); chars = splitChars; stringBuilder.push("
", "
"); split ++; splitChars = splits[split] || Number.MAX_VALUE; } if (value.length != 0) { chars += value.length; addToken(token, value); } } }; if (this.showInvisibles) { if (row !== this.session.getLength() - 1) { stringBuilder.push("" + this.EOL_CHAR + ""); } else { stringBuilder.push("" + this.EOF_CHAR + ""); } } stringBuilder.push("
"); }; }).call(Text.prototype); exports.Text = Text; }); /* vim:ts=4:sts=4:sw=4: * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Ajax.org Code Editor (ACE). * * The Initial Developer of the Original Code is * Ajax.org B.V. * Portions created by the Initial Developer are Copyright (C) 2010 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Fabian Jakobs * Julian Viereck * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('ace/layer/cursor', function(require, exports, module) { var dom = require("pilot/dom"); var Cursor = function(parentEl) { this.element = dom.createElement("div"); this.element.className = "ace_layer ace_cursor-layer"; parentEl.appendChild(this.element); this.cursor = dom.createElement("div"); this.cursor.className = "ace_cursor"; this.isVisible = false; }; (function() { this.setSession = function(session) { this.session = session; }; this.hideCursor = function() { this.isVisible = false; if (this.cursor.parentNode) { this.cursor.parentNode.removeChild(this.cursor); } clearInterval(this.blinkId); }; this.showCursor = function() { this.isVisible = true; this.element.appendChild(this.cursor); var cursor = this.cursor; cursor.style.visibility = "visible"; this.restartTimer(); }; this.restartTimer = function() { clearInterval(this.blinkId); if (!this.isVisible) { return; } var cursor = this.cursor; this.blinkId = setInterval(function() { cursor.style.visibility = "hidden"; setTimeout(function() { cursor.style.visibility = "visible"; }, 400); }, 1000); }; this.getPixelPosition = function(onScreen) { if (!this.config || !this.session) { return { left : 0, top : 0 }; } var position = this.session.selection.getCursor(); var pos = this.session.documentToScreenPosition(position); var cursorLeft = Math.round(pos.column * this.config.characterWidth); var cursorTop = (pos.row - (onScreen ? this.config.firstRowScreen : 0)) * this.config.lineHeight; return { left : cursorLeft, top : cursorTop }; }; this.update = function(config) { this.config = config; this.pixelPos = this.getPixelPosition(true); this.cursor.style.left = this.pixelPos.left + "px"; this.cursor.style.top = this.pixelPos.top + "px"; this.cursor.style.width = config.characterWidth + "px"; this.cursor.style.height = config.lineHeight + "px"; if (this.isVisible) { this.element.appendChild(this.cursor); } if (this.session.getOverwrite()) { dom.addCssClass(this.cursor, "ace_overwrite"); } else { dom.removeCssClass(this.cursor, "ace_overwrite"); } this.restartTimer(); }; }).call(Cursor.prototype); exports.Cursor = Cursor; }); /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Ajax.org Code Editor (ACE). * * The Initial Developer of the Original Code is * Ajax.org B.V. * Portions created by the Initial Developer are Copyright (C) 2010 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Fabian Jakobs * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('ace/scrollbar', function(require, exports, module) { var oop = require("pilot/oop"); var dom = require("pilot/dom"); var event = require("pilot/event"); var EventEmitter = require("pilot/event_emitter").EventEmitter; var ScrollBar = function(parent) { this.element = dom.createElement("div"); this.element.className = "ace_sb"; this.inner = dom.createElement("div"); this.element.appendChild(this.inner); parent.appendChild(this.element); this.width = dom.scrollbarWidth(); this.element.style.width = this.width + "px"; event.addListener(this.element, "scroll", this.onScroll.bind(this)); }; (function() { oop.implement(this, EventEmitter); this.onScroll = function() { this._dispatchEvent("scroll", {data: this.element.scrollTop}); }; this.getWidth = function() { return this.width; }; this.setHeight = function(height) { this.element.style.height = height + "px"; }; this.setInnerHeight = function(height) { this.inner.style.height = height + "px"; }; this.setScrollTop = function(scrollTop) { this.element.scrollTop = scrollTop; }; }).call(ScrollBar.prototype); exports.ScrollBar = ScrollBar; }); /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Ajax.org Code Editor (ACE). * * The Initial Developer of the Original Code is * Ajax.org B.V. * Portions created by the Initial Developer are Copyright (C) 2010 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Fabian Jakobs * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('ace/renderloop', function(require, exports, module) { var event = require("pilot/event"); var RenderLoop = function(onRender) { this.onRender = onRender; this.pending = false; this.changes = 0; }; (function() { this.schedule = function(change) { //this.onRender(change); //return; this.changes = this.changes | change; if (!this.pending) { this.pending = true; var _self = this; this.setTimeoutZero(function() { _self.pending = false; var changes = _self.changes; _self.changes = 0; _self.onRender(changes); }) } }; if (window.postMessage) { this.messageName = "zero-timeout-message"; this.setTimeoutZero = function(callback) { if (!this.attached) { var _self = this; event.addListener(window, "message", function(e) { if (_self.callback && e.data == _self.messageName) { event.stopPropagation(e); _self.callback(); } }); this.attached = true; } this.callback = callback; window.postMessage(this.messageName, "*"); } } else { this.setTimeoutZero = function(callback) { setTimeout(callback, 0); } } }).call(RenderLoop.prototype); exports.RenderLoop = RenderLoop; }); define("text/ace/css/editor.css", ".ace_editor {" + " position: absolute;" + " overflow: hidden;" + "" + " font-family: \"Menlo\", \"Monaco\", \"Courier New\", monospace;" + " font-size: 12px; " + "}" + "" + ".ace_scroller {" + " position: absolute;" + " overflow-x: scroll;" + " overflow-y: hidden; " + "}" + "" + ".ace_content {" + " position: absolute;" + " box-sizing: border-box;" + " -moz-box-sizing: border-box;" + " -webkit-box-sizing: border-box;" + "}" + "" + ".ace_composition {" + " position: absolute;" + " background: #555;" + " color: #DDD;" + " z-index: 4;" + "}" + "" + ".ace_gutter {" + " position: absolute;" + " overflow-x: hidden;" + " overflow-y: hidden;" + " height: 100%;" + "}" + "" + ".ace_gutter-cell.ace_error {" + " background-image: url(\"data:image/gif,GIF89a%10%00%10%00%D5%00%00%F5or%F5%87%88%F5nr%F4ns%EBmq%F5z%7F%DDJT%DEKS%DFOW%F1Yc%F2ah%CE(7%CE)8%D18E%DD%40M%F2KZ%EBU%60%F4%60m%DCir%C8%16(%C8%19*%CE%255%F1%3FR%F1%3FS%E6%AB%B5%CA%5DI%CEn%5E%F7%A2%9A%C9G%3E%E0a%5B%F7%89%85%F5yy%F6%82%80%ED%82%80%FF%BF%BF%E3%C4%C4%FF%FF%FF%FF%FF%FF%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00!%F9%04%01%00%00%25%00%2C%00%00%00%00%10%00%10%00%00%06p%C0%92pH%2C%1A%8F%C8%D2H%93%E1d4%23%E4%88%D3%09mB%1DN%B48%F5%90%40%60%92G%5B%94%20%3E%22%D2%87%24%FA%20%24%C5%06A%00%20%B1%07%02B%A38%89X.v%17%82%11%13q%10%0Fi%24%0F%8B%10%7BD%12%0Ei%09%92%09%0EpD%18%15%24%0A%9Ci%05%0C%18F%18%0B%07%04%01%04%06%A0H%18%12%0D%14%0D%12%A1I%B3%B4%B5IA%00%3B\");" + " background-repeat: no-repeat;" + " background-position: 4px center;" + "}" + "" + ".ace_gutter-cell.ace_warning {" + " background-image: url(\"data:image/gif,GIF89a%10%00%10%00%D5%00%00%FF%DBr%FF%DE%81%FF%E2%8D%FF%E2%8F%FF%E4%96%FF%E3%97%FF%E5%9D%FF%E6%9E%FF%EE%C1%FF%C8Z%FF%CDk%FF%D0s%FF%D4%81%FF%D5%82%FF%D5%83%FF%DC%97%FF%DE%9D%FF%E7%B8%FF%CCl%7BQ%13%80U%15%82W%16%81U%16%89%5B%18%87%5B%18%8C%5E%1A%94d%1D%C5%83-%C9%87%2F%C6%84.%C6%85.%CD%8B2%C9%871%CB%8A3%CD%8B5%DC%98%3F%DF%9BB%E0%9CC%E1%A5U%CB%871%CF%8B5%D1%8D6%DB%97%40%DF%9AB%DD%99B%E3%B0p%E7%CC%AE%FF%FF%FF%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00!%F9%04%01%00%00%2F%00%2C%00%00%00%00%10%00%10%00%00%06a%C0%97pH%2C%1A%8FH%A1%ABTr%25%87%2B%04%82%F4%7C%B9X%91%08%CB%99%1C!%26%13%84*iJ9(%15G%CA%84%14%01%1A%97%0C%03%80%3A%9A%3E%81%84%3E%11%08%B1%8B%20%02%12%0F%18%1A%0F%0A%03'F%1C%04%0B%10%16%18%10%0B%05%1CF%1D-%06%07%9A%9A-%1EG%1B%A0%A1%A0U%A4%A5%A6BA%00%3B\");" + " background-repeat: no-repeat;" + " background-position: 4px center;" + "}" + "" + ".ace_editor .ace_sb {" + " position: absolute;" + " overflow-x: hidden;" + " overflow-y: scroll;" + " right: 0;" + "}" + "" + ".ace_editor .ace_sb div {" + " position: absolute;" + " width: 1px;" + " left: 0;" + "}" + "" + ".ace_editor .ace_print_margin_layer {" + " z-index: 0;" + " position: absolute;" + " overflow: hidden;" + " margin: 0;" + " left: 0;" + " height: 100%;" + " width: 100%;" + "}" + "" + ".ace_editor .ace_print_margin {" + " position: absolute;" + " height: 100%;" + "}" + "" + ".ace_editor textarea {" + " position: fixed;" + " z-index: -1;" + " width: 10px;" + " height: 30px;" + " opacity: 0;" + " background: transparent;" + " appearance: none;" + " border: none;" + " resize: none;" + " outline: none;" + " overflow: hidden;" + "}" + "" + ".ace_layer {" + " z-index: 1;" + " position: absolute;" + " overflow: hidden; " + " white-space: nowrap;" + " height: 100%;" + " width: 100%;" + "}" + "" + ".ace_text-layer {" + " font-family: Monaco, \"Courier New\", monospace;" + " color: black;" + "}" + "" + ".ace_cjk {" + " display: inline-block;" + " text-align: center;" + "}" + "" + ".ace_cursor-layer {" + " z-index: 4;" + " cursor: text;" + " pointer-events: none;" + "}" + "" + ".ace_cursor {" + " z-index: 4;" + " position: absolute;" + "}" + "" + ".ace_line {" + " white-space: nowrap;" + "}" + "" + ".ace_marker-layer {" + " cursor: text;" + "}" + "" + ".ace_marker-layer .ace_step {" + " position: absolute;" + " z-index: 3;" + "}" + "" + ".ace_marker-layer .ace_selection {" + " position: absolute;" + " z-index: 4;" + "}" + "" + ".ace_marker-layer .ace_bracket {" + " position: absolute;" + " z-index: 5;" + "}" + "" + ".ace_marker-layer .ace_active_line {" + " position: absolute;" + " z-index: 2;" + "}" + "" + ".ace_marker-layer .ace_selected_word {" + " position: absolute;" + " z-index: 6;" + " box-sizing: border-box;" + " -moz-box-sizing: border-box;" + " -webkit-box-sizing: border-box;" + "}" + "" + ".ace_dragging .ace_marker-layer, .ace_dragging .ace_text-layer {" + " cursor: move;" + "}" + ""); define("text/styles.css", "html {" + " height: 100%;" + " overflow: hidden;" + "}" + "" + "body {" + " overflow: hidden;" + " margin: 0;" + " padding: 0;" + " height: 100%;" + " width: 100%;" + " font-family: Arial, Helvetica, sans-serif, Tahoma, Verdana, sans-serif;" + " font-size: 12px;" + " background: rgb(14, 98, 165);" + " color: white;" + "}" + "" + "#editor {" + " position: absolute;" + " top: 60px;" + " left: 0px;" + " background: white;" + "}" + "" + "#controls {" + " width: 100%;" + "}" + "" + "#cockpitInput {" + " position: absolute;" + " width: 100%;" + " bottom: 0;" + "" + " border: none; outline: none;" + " font-family: consolas, courier, monospace;" + " font-size: 120%;" + "}" + "" + "#cockpitOutput {" + " padding: 10px;" + " margin: 0 15px;" + " border: 1px solid #AAA;" + " -moz-border-radius-topleft: 10px;" + " -moz-border-radius-topright: 10px;" + " border-top-left-radius: 4px; border-top-right-radius: 4px;" + " background: #DDD; color: #000;" + "}"); /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Mozilla Skywriter. * * The Initial Developer of the Original Code is * Mozilla. * Portions created by the Initial Developer are Copyright (C) 2009 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Kevin Dangoor (kdangoor@mozilla.com) * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ var deps = [ "pilot/fixoldbrowsers", "pilot/index", "pilot/plugin_manager", "pilot/environment", "ace/editor", "ace/edit_session", "ace/virtual_renderer", "ace/undomanager", "ace/theme/textmate" ]; require(deps, function() { var catalog = require("pilot/plugin_manager").catalog; catalog.registerPlugins([ "pilot/index" ]); var Dom = require("pilot/dom"); var Event = require("pilot/event"); var Editor = require("ace/editor").Editor; var EditSession = require("ace/edit_session").EditSession; var UndoManager = require("ace/undomanager").UndoManager; var Renderer = require("ace/virtual_renderer").VirtualRenderer; window.ace = { edit: function(el) { if (typeof(el) == "string") { el = document.getElementById(el); } var doc = new EditSession(Dom.getInnerText(el)); doc.setUndoManager(new UndoManager()); el.innerHTML = ''; var editor = new Editor(new Renderer(el, "ace/theme/textmate")); editor.setSession(doc); var env = require("pilot/environment").create(); catalog.startupPlugins({ env: env }).then(function() { env.document = doc; env.editor = editor; editor.resize(); Event.addListener(window, "resize", function() { editor.resize(); }); el.env = env; }); // Store env on editor such that it can be accessed later on from // the returned object. editor.env = env; return editor; } }; }); ================================================ FILE: dirigible/shared/static/ace/ace.js ================================================ (function(){if(window.require)require.packaged=!0;else{var a=function(b,c,d){typeof b!=="string"?a.original?a.original.apply(window,arguments):(console.error("dropping module because define wasn't a string."),console.trace()):(arguments.length==2&&(d=c),define.modules||(define.modules={}),define.modules[b]=d)};window.define&&(a.original=window.define),window.define=a;var b=function(a,d){if(Object.prototype.toString.call(a)==="[object Array]"){var e=[];for(var f=0,g=a.length;f=2)var d=arguments[1];else do{if(c in this){d=this[c++];break}if(++c>=b)throw new TypeError}while(!0);for(;c=2)var d=arguments[1];else do{if(c in this){d=this[c--];break}if(--c<0)throw new TypeError}while(!0);for(;c>=0;c--)c in this&&(d=a.call(null,d,this[c],c,this));return d}),Array.prototype.indexOf||(Array.prototype.indexOf=function(a){var b=this.length;if(!b)return-1;var c=arguments[1]||0;if(c>=b)return-1;c<0&&(c+=b);for(;c=0;c--){if(!d.call(this,c))continue;if(a===this[c])return c}return-1}),Object.getPrototypeOf||(Object.getPrototypeOf=function(a){return a.__proto__||a.constructor.prototype}),Object.getOwnPropertyDescriptor||(Object.getOwnPropertyDescriptor=function(a,b){if(typeof a!=="object"&&typeof a!=="function"||a===null)throw new TypeError("Object.getOwnPropertyDescriptor called on a non-object");return d.call(a,b)?{value:a[b],enumerable:!0,configurable:!0,writeable:!0}:undefined}),Object.getOwnPropertyNames||(Object.getOwnPropertyNames=function(a){return Object.keys(a)}),Object.create||(Object.create=function(a,b){var c;if(a===null)c={"__proto__":null};else{if(typeof a!="object")throw new TypeError("typeof prototype["+typeof a+"] != 'object'");var d=function(){};d.prototype=a,c=new d}typeof b!=="undefined"&&Object.defineProperties(c,b);return c}),Object.defineProperty||(Object.defineProperty=function(a,b,c){if(typeof c=="object"&&a.__defineGetter__){if(d.call(c,"value")){!a.__lookupGetter__(b)&&!a.__lookupSetter__(b)&&(a[b]=c.value);if(d.call(c,"get")||d.call(c,"set"))throw new TypeError("Object doesn't support this action")}else typeof c.get=="function"&&a.__defineGetter__(b,c.get);typeof c.set=="function"&&a.__defineSetter__(b,c.set)}return a}),Object.defineProperties||(Object.defineProperties=function(a,b){for(var c in b)d.call(b,c)&&Object.defineProperty(a,c,b[c]);return a}),Object.seal||(Object.seal=function(a){return a}),Object.freeze||(Object.freeze=function(a){return a});try{Object.freeze(function(){})}catch(e){Object.freeze=function(a){return function(b){return typeof b=="function"?b:a(b)}}(Object.freeze)}Object.preventExtensions||(Object.preventExtensions=function(a){return a}),Object.isSealed||(Object.isSealed=function(a){return!1}),Object.isFrozen||(Object.isFrozen=function(a){return!1}),Object.isExtensible||(Object.isExtensible=function(a){return!0});if(!Object.keys){var f=!0,g=["toString","toLocaleString","valueOf","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","constructor"],h=g.length;for(var i in {toString:null})f=!1;Object.keys=function(a){if(typeof a!=="object"&&typeof a!=="function"||a===null)throw new TypeError("Object.keys called on a non-object");var b=[];for(var c in a)d.call(a,c)&&b.push(c);if(f)for(var e=0,i=h;e=7?new a(c,d,e,f,g,h,i):j>=6?new a(c,d,e,f,g,h):j>=5?new a(c,d,e,f,g):j>=4?new a(c,d,e,f):j>=3?new a(c,d,e):j>=2?new a(c,d):j>=1?new a(c):new a;k.constructor=b;return k}return a.apply(this,arguments)},c=new RegExp("^(?:((?:[+-]\\d\\d)?\\d\\d\\d\\d)(?:-(\\d\\d)(?:-(\\d\\d))?)?)?(?:T(\\d\\d):(\\d\\d)(?::(\\d\\d)(?:\\.(\\d\\d\\d))?)?)?(?:Z|([+-])(\\d\\d):(\\d\\d))?$");for(var d in a)b[d]=a[d];b.now=a.now,b.UTC=a.UTC,b.prototype=a.prototype,b.prototype.constructor=b,b.parse=function(b){var d=c.exec(b);if(d){d.shift();var e=d[0]===undefined;for(var f=0;f<10;f++){if(f===7)continue;d[f]=+(d[f]||(f<3?1:0)),f===1&&d[f]--}if(e)return((d[3]*60+d[4])*60+d[5])*1e3+d[6];var g=(d[8]*60+d[9])*60*1e3;d[6]==="-"&&(g=-g);return a.UTC.apply(this,d.slice(0,7))+g}return a.parse.apply(this,arguments)};return b}(Date));var j=Array.prototype.slice;Function.prototype.bind||(Function.prototype.bind=function(a){var b=this;if(typeof b.apply!="function"||typeof b.call!="function")return new TypeError;var c=j.call(arguments),d=function(){if(this instanceof d){var a=Object.create(b.prototype);b.apply(a,c.concat(j.call(arguments)));return a}return b.call.apply(b,c.concat(j.call(arguments)))};d.bound=b,d.boundTo=a,d.boundArgs=c,d.length=typeof b=="function"?Math.max(b.length-c.length,0):0;return d});if(!String.prototype.trim){var k=/^\s\s*/,l=/\s\s*$/;String.prototype.trim=function(){return String(this).replace(k,"").replace(l,"")}}b.globalsLoaded=!0}),define("pilot/index",function(a,b,c){b.startup=function(b,c){a("pilot/fixoldbrowsers"),a("pilot/types/basic").startup(b,c),a("pilot/types/command").startup(b,c),a("pilot/types/settings").startup(b,c),a("pilot/commands/settings").startup(b,c),a("pilot/commands/basic").startup(b,c),a("pilot/settings/canon").startup(b,c),a("pilot/canon").startup(b,c)},b.shutdown=function(b,c){a("pilot/types/basic").shutdown(b,c),a("pilot/types/command").shutdown(b,c),a("pilot/types/settings").shutdown(b,c),a("pilot/commands/settings").shutdown(b,c),a("pilot/commands/basic").shutdown(b,c),a("pilot/settings/canon").shutdown(b,c),a("pilot/canon").shutdown(b,c)}}),define("pilot/types/basic",function(a,b,c){function m(a){if(a instanceof e)this.subtype=a;else{if(typeof a!=="string")throw new Error("Can' handle array subtype");this.subtype=d.getType(a);if(this.subtype==null)throw new Error("Unknown array subtype: "+a)}}function l(a){if(typeof a.defer!=="function")throw new Error("Instances of DeferredType need typeSpec.defer to be a function that returns a type");Object.keys(a).forEach(function(b){this[b]=a[b]},this)}function j(a){if(!Array.isArray(a.data)&&typeof a.data!=="function")throw new Error("instances of SelectionType need typeSpec.data to be an array or function that returns an array:"+JSON.stringify(a));Object.keys(a).forEach(function(b){this[b]=a[b]},this)}var d=a("pilot/types"),e=d.Type,f=d.Conversion,g=d.Status,h=new e;h.stringify=function(a){return a},h.parse=function(a){if(typeof a!="string")throw new Error("non-string passed to text.parse()");return new f(a)},h.name="text";var i=new e;i.stringify=function(a){if(!a)return null;return""+a},i.parse=function(a){if(typeof a!="string")throw new Error("non-string passed to number.parse()");if(a.replace(/\s/g,"").length===0)return new f(null,g.INCOMPLETE,"");var b=new f(parseInt(a,10));isNaN(b.value)&&(b.status=g.INVALID,b.message="Can't convert \""+a+'" to a number.');return b},i.decrement=function(a){return a-1},i.increment=function(a){return a+1},i.name="number",j.prototype=new e,j.prototype.stringify=function(a){return a},j.prototype.parse=function(a){if(typeof a!="string")throw new Error("non-string passed to parse()");if(!this.data)throw new Error("Missing data on selection type extension.");var b=typeof this.data==="function"?this.data():this.data,c=!1,d,e=[];b.forEach(function(b){a==b?(d=this.fromString(b),c=!0):b.indexOf(a)===0&&e.push(this.fromString(b))},this);if(c)return new f(d);this.noMatch&&this.noMatch();if(e.length>0){var h="Possibilities"+(a.length===0?"":" for '"+a+"'");return new f(null,g.INCOMPLETE,h,e)}var h="Can't use '"+a+"'.";return new f(null,g.INVALID,h,e)},j.prototype.fromString=function(a){return a},j.prototype.decrement=function(a){var b=typeof this.data==="function"?this.data():this.data,c;if(a==null)c=b.length-1;else{var d=this.stringify(a),c=b.indexOf(d);c=c===0?b.length-1:c-1}return this.fromString(b[c])},j.prototype.increment=function(a){var b=typeof this.data==="function"?this.data():this.data,c;if(a==null)c=0;else{var d=this.stringify(a),c=b.indexOf(d);c=c===b.length-1?0:c+1}return this.fromString(b[c])},j.prototype.name="selection",b.SelectionType=j;var k=new j({name:"bool",data:["true","false"],stringify:function(a){return""+a},fromString:function(a){return a==="true"?!0:!1}});l.prototype=new e,l.prototype.stringify=function(a){return this.defer().stringify(a)},l.prototype.parse=function(a){return this.defer().parse(a)},l.prototype.decrement=function(a){var b=this.defer();return b.decrement?b.decrement(a):undefined},l.prototype.increment=function(a){var b=this.defer();return b.increment?b.increment(a):undefined},l.prototype.name="deferred",b.DeferredType=l,m.prototype=new e,m.prototype.stringify=function(a){return a.join(" ")},m.prototype.parse=function(a){return this.defer().parse(a)},m.prototype.name="array";var n=!1;b.startup=function(){n||(n=!0,d.registerType(h),d.registerType(i),d.registerType(k),d.registerType(j),d.registerType(l),d.registerType(m))},b.shutdown=function(){n=!1,d.unregisterType(h),d.unregisterType(i),d.unregisterType(k),d.unregisterType(j),d.unregisterType(l),d.unregisterType(m)}}),define("pilot/types",function(a,b,c){function i(a,b){if(a.substr(-2)==="[]"){var c=a.slice(0,-2);return new g.array(c)}var d=g[a];typeof d==="function"&&(d=new d(b));return d}function f(){}function e(a,b,c,e){this.value=a,this.status=b||d.VALID,this.message=c,this.predictions=e||[]}var d={VALID:{toString:function(){return"VALID"},valueOf:function(){return 0}},INCOMPLETE:{toString:function(){return"INCOMPLETE"},valueOf:function(){return 1}},INVALID:{toString:function(){return"INVALID"},valueOf:function(){return 2}},combine:function(a){var b=d.VALID;for(var c=0;cb.valueOf()&&(b=a[c]);return b}};b.Status=d,b.Conversion=e,f.prototype={stringify:function(a){throw new Error("not implemented")},parse:function(a){throw new Error("not implemented")},name:undefined,increment:function(a){return undefined},decrement:function(a){return undefined},getDefault:function(){return this.parse("")}},b.Type=f;var g={};b.registerType=function(a){if(typeof a==="object"){if(!(a instanceof f))throw new Error("Can't registerType using: "+a);if(!a.name)throw new Error("All registered types must have a name");g[a.name]=a}else{if(typeof a!=="function")throw new Error("Unknown type: "+a);if(!a.prototype.name)throw new Error("All registered types must have a name");g[a.prototype.name]=a}},b.registerTypes=function h(a){Object.keys(a).forEach(function(c){var d=a[c];d.name=c,b.registerType(d)})},b.deregisterType=function(a){delete g[a.name]},b.getType=function(a){if(typeof a==="string")return i(a);if(typeof a==="object"){if(!a.name)throw new Error("Missing 'name' member to typeSpec");return i(a.name,a)}throw new Error("Can't extract type from "+a)}}),define("pilot/types/command",function(a,b,c){var d=a("pilot/canon"),e=a("pilot/types/basic").SelectionType,f=a("pilot/types"),g=new e({name:"command",data:function(){return d.getCommandNames()},stringify:function(a){return a.name},fromString:function(a){return d.getCommand(a)}});b.startup=function(){f.registerType(g)},b.shutdown=function(){f.unregisterType(g)}}),define("pilot/canon",function(a,b,c){function J(a){a=a||{},this.command=a.command,this.args=a.args,this.typed=a.typed,this._begunOutput=!1,this.start=new Date,this.end=null,this.completed=!1,this.error=!1}function G(a,b,c,e,f){function h(){a.exec(b,g.args,g),!g.isAsync&&!g.isDone&&g.done()}typeof a==="string"&&(a=q[a]);if(!a)return!1;var g=new J({sender:c,command:a,args:e||{},typed:f});if(g.getStatus()==l.INVALID){d.error("Canon.exec: Invalid parameter(s) passed to "+a.name);return!1}if(g.getStatus()==l.INCOMPLETE){var i,j=b[c];if(!j||!j.getArgsProvider||!(i=j.getArgsProvider()))i=F;i(g,function(){g.getStatus()==l.VALID&&h()});return!0}h();return!0}function F(a,b){var c=a.args,d=a.command.params;for(var e=0;eI)H.shiftObject();b._dispatchEvent("output",{requests:H,request:this})},J.prototype.doneWithError=function(a){this.error=!0,this.done(a)},J.prototype.async=function(){this.isAsync=!0,this._begunOutput||this._beginOutput()},J.prototype.output=function(a){this._begunOutput||this._beginOutput(),typeof a!=="string"&&!(a instanceof Node)&&(a=a.toString()),this.outputs.push(a),this.isDone=!0,this._dispatchEvent("output",{});return this},J.prototype.done=function(a){this.completed=!0,this.end=new Date,this.duration=this.end.getTime()-this.start.getTime(),a&&this.output(a),this.isDone||(this.isDone=!0,this._dispatchEvent("output",{}))},b.Request=J}),define("pilot/console",function(a,b,c){var d=function(){},e=["assert","count","debug","dir","dirxml","error","group","groupEnd","info","log","profile","profileEnd","time","timeEnd","trace","warn"];typeof window==="undefined"?e.forEach(function(a){b[a]=function(){var b=Array.prototype.slice.call(arguments),c={op:"log",method:a,args:b};postMessage(JSON.stringify(c))}}):e.forEach(function(a){window.console&&window.console[a]?b[a]=Function.prototype.bind.call(window.console[a],window.console):b[a]=d})}),define("pilot/stacktrace",function(a,b,c){function i(){}function g(a){for(var b=0;b\s*\(/gm,"{anonymous}()@").split("\n")},firefox:function(a){var b=a.stack;if(!b){e.log(a);return[]}b=b.replace(/(?:\n@:0)?\s+$/m,""),b=b.replace(/^\(/gm,"{anonymous}(");return b.split("\n")},opera:function(a){var b=a.message.split("\n"),c="{anonymous}",d=/Line\s+(\d+).*?script\s+(http\S+)(?:.*?in\s+function\s+(\S+))?/i,e,f,g;for(e=4,f=0,g=b.length;e=0,b.isIPad=e.indexOf("iPad")>=0,b.OS={LINUX:"LINUX",MAC:"MAC",WINDOWS:"WINDOWS"},b.getOS=function(){return b.isMac?b.OS.MAC:b.isLinux?b.OS.LINUX:b.OS.WINDOWS}}),define("pilot/oop",function(a,b,c){b.inherits=function(){var a=function(){};return function(b,c){a.prototype=c.prototype,b.super_=c.prototype,b.prototype=new a,b.prototype.constructor=b}}(),b.mixin=function(a,b){for(var c in b)a[c]=b[c]},b.implement=function(a,c){b.mixin(a,c)}}),define("pilot/keys",function(a,b,c){var d=a("pilot/oop"),e=function(){var a={MODIFIER_KEYS:{16:"Shift",17:"Ctrl",18:"Alt",224:"Meta"},KEY_MODS:{ctrl:1,alt:2,option:2,shift:4,meta:8,command:8},FUNCTION_KEYS:{8:"Backspace",9:"Tab",13:"Return",19:"Pause",27:"Esc",32:"Space",33:"PageUp",34:"PageDown",35:"End",36:"Home",37:"Left",38:"Up",39:"Right",40:"Down",44:"Print",45:"Insert",46:"Delete",112:"F1",113:"F2",114:"F3",115:"F4",116:"F5",117:"F6",118:"F7",119:"F8",120:"F9",121:"F10",122:"F11",123:"F12",144:"Numlock",145:"Scrolllock"},PRINTABLE_KEYS:{32:" ",48:"0",49:"1",50:"2",51:"3",52:"4",53:"5",54:"6",55:"7",56:"8",57:"9",59:";",61:"=",65:"a",66:"b",67:"c",68:"d",69:"e",70:"f",71:"g",72:"h",73:"i",74:"j",75:"k",76:"l",77:"m",78:"n",79:"o",80:"p",81:"q",82:"r",83:"s",84:"t",85:"u",86:"v",87:"w",88:"x",89:"y",90:"z",107:"+",109:"-",110:".",188:",",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:'"'}};for(i in a.FUNCTION_KEYS){var b=a.FUNCTION_KEYS[i].toUpperCase();a[b]=parseInt(i,10)}d.mixin(a,a.MODIFIER_KEYS),d.mixin(a,a.PRINTABLE_KEYS),d.mixin(a,a.FUNCTION_KEYS);return a}();d.mixin(b,e),b.keyCodeToString=function(a){return(e[a]||String.fromCharCode(a)).toLowerCase()}}),define("pilot/event_emitter",function(a,b,c){var d={};d._emit=d._dispatchEvent=function(a,b){this._eventRegistry=this._eventRegistry||{};var c=this._eventRegistry[a];if(!!c&&!!c.length){var b=b||{};b.type=a;for(var d=0;d'+c.name+" = "+c.value+"
"})}else b.value===undefined?d=""+setting.name+" = "+setting.get():(b.setting.set(b.value),d="Setting: "+b.setting.name+" = "+b.setting.get());c.done(d)}},e={name:"unset",params:[{name:"setting",type:"setting",description:"The name of the setting to return to defaults"}],description:"unset a setting entirely",exec:function(a,b,c){var d=a.settings.get(b.setting);d?(d.reset(),c.done("Reset "+d.name+" to default: "+a.settings.get(b.setting))):c.doneWithError("No setting with the name "+b.setting+".")}},f=a("pilot/canon");b.startup=function(a,b){f.addCommand(d),f.addCommand(e)},b.shutdown=function(a,b){f.removeCommand(d),f.removeCommand(e)}}),define("pilot/commands/basic",function(require,exports,module){var checks=require("pilot/typecheck"),canon=require("pilot/canon"),helpMessages={plainPrefix:'

Welcome to Skywriter - Code in the Cloud

',plainSuffix:'For more information, see the Skywriter Wiki.'},helpCommandSpec={name:"help",params:[{name:"search",type:"text",description:"Search string to narrow the output.",defaultValue:null}],description:"Get help on the available commands.",exec:function(a,b,c){var d=[],e=canon.getCommand(b.search);if(e&&e.exec)d.push(e.description?e.description:"No description for "+b.search);else{var f=!1;!b.search&&helpMessages.plainPrefix&&d.push(helpMessages.plainPrefix),e?(d.push("

Sub-Commands of "+e.name+"

"),d.push("

"+e.description+"

")):b.search?(b.search=="hidden"&&(b.search="",f=!0),d.push("

Commands starting with '"+b.search+"':

")):d.push("

Available Commands:

");var g=canon.getCommandNames();g.sort(),d.push("");for(var h=0;h"),d.push('"),d.push(""),d.push("")}d.push("
'+e.name+""+e.description+"
"),!b.search&&helpMessages.plainSuffix&&d.push(helpMessages.plainSuffix)}c.done(d.join(""))}},evalCommandSpec={name:"eval",params:[{name:"javascript",type:"text",description:"The JavaScript to evaluate"}],description:"evals given js code and show the result",hidden:!0,exec:function(env,args,request){var result,javascript=args.javascript;try{result=eval(javascript)}catch(e){result="Error: "+e.message+""}var msg="",type="",x;if(checks.isFunction(result))msg=(result+"").replace(/\n/g,"
").replace(/ /g," "),type="function";else if(checks.isObject(result)){Array.isArray(result)?type="array":type="object";var items=[],value;for(x in result)result.hasOwnProperty(x)&&(checks.isFunction(result[x])?value="[function]":checks.isObject(result[x])?value="[object]":value=result[x],items.push({name:x,value:value}));items.sort(function(a,b){return a.name.toLowerCase()"+items[x].name+": "+items[x].value+"
"}else msg=result,type=typeof result;request.done("Result for eval '"+javascript+"'"+" (type: "+type+"):

"+msg)}},versionCommandSpec={name:"version",description:"show the Skywriter version",hidden:!0,exec:function(a,b,c){var d="Skywriter "+skywriter.versionNumber+" ("+skywriter.versionCodename+")";c.done(d)}},skywriterCommandSpec={name:"skywriter",hidden:!0,exec:function(a,b,c){var d=Math.floor(Math.random()*messages.length);c.done("Skywriter "+messages[d])}},messages=["really wants you to trick it out in some way.","is your Web editor.","would love to be like Emacs on the Web.","is written on the Web platform, so you can tweak it."],canon=require("pilot/canon");exports.startup=function(a,b){canon.addCommand(helpCommandSpec),canon.addCommand(evalCommandSpec),canon.addCommand(skywriterCommandSpec)},exports.shutdown=function(a,b){canon.removeCommand(helpCommandSpec),canon.removeCommand(evalCommandSpec),canon.removeCommand(skywriterCommandSpec)}}),define("pilot/settings/canon",function(a,b,c){var d={name:"historyLength",description:"How many typed commands do we recall for reference?",type:"number",defaultValue:50};b.startup=function(a,b){a.env.settings.addSetting(d)},b.shutdown=function(a,b){a.env.settings.removeSetting(d)}}),define("pilot/plugin_manager",function(a,b,c){var d=a("pilot/promise").Promise;b.REASONS={APP_STARTUP:1,APP_SHUTDOWN:2,PLUGIN_ENABLE:3,PLUGIN_DISABLE:4,PLUGIN_INSTALL:5,PLUGIN_UNINSTALL:6,PLUGIN_UPGRADE:7,PLUGIN_DOWNGRADE:8},b.Plugin=function(a){this.name=a,this.status=this.INSTALLED},b.Plugin.prototype={NEW:0,INSTALLED:1,REGISTERED:2,STARTED:3,UNREGISTERED:4,SHUTDOWN:5,install:function(b,c){var e=new d;if(this.status>this.NEW){e.resolve(this);return e}a([this.name],function(a){a.install&&a.install(b,c),this.status=this.INSTALLED,e.resolve(this)}.bind(this));return e},register:function(b,c){var e=new d;if(this.status!=this.INSTALLED){e.resolve(this);return e}a([this.name],function(a){a.register&&a.register(b,c),this.status=this.REGISTERED,e.resolve(this)}.bind(this));return e},startup:function(c,e){e=e||b.REASONS.APP_STARTUP;var f=new d;if(this.status!=this.REGISTERED){f.resolve(this);return f}a([this.name],function(a){a.startup&&a.startup(c,e),this.status=this.STARTED,f.resolve(this)}.bind(this));return f},shutdown:function(b,c){this.status==this.STARTED&&(pluginModule=a(this.name),pluginModule.shutdown&&pluginModule.shutdown(b,c))}},b.PluginCatalog=function(){this.plugins={}},b.PluginCatalog.prototype={registerPlugins:function(a,c,e){var f=[];a.forEach(function(a){var d=this.plugins[a];d===undefined&&(d=new b.Plugin(a),this.plugins[a]=d,f.push(d.register(c,e)))}.bind(this));return d.group(f)},startupPlugins:function(a,b){var c=[];for(var e in this.plugins){var f=this.plugins[e];c.push(f.startup(a,b))}return d.group(c)}},b.catalog=new b.PluginCatalog}),define("pilot/promise",function(a,b,c){var d=a("pilot/console"),e=a("pilot/stacktrace").Trace,f=-1,g=0,h=1,i=0,j=!1,k=[],l=[];Promise=function(){this._status=g,this._value=undefined,this._onSuccessHandlers=[],this._onErrorHandlers=[],this._id=i++,k[this._id]=this},Promise.prototype.isPromise=!0,Promise.prototype.isComplete=function(){return this._status!=g},Promise.prototype.isResolved=function(){return this._status==h},Promise.prototype.isRejected=function(){return this._status==f},Promise.prototype.then=function(a,b){typeof a==="function"&&(this._status===h?a.call(null,this._value):this._status===g&&this._onSuccessHandlers.push(a)),typeof b==="function"&&(this._status===f?b.call(null,this._value):this._status===g&&this._onErrorHandlers.push(b));return this},Promise.prototype.chainPromise=function(a){var b=new Promise;b._chainedFrom=this,this.then(function(c){try{b.resolve(a(c))}catch(d){b.reject(d)}},function(a){b.reject(a)});return b},Promise.prototype.resolve=function(a){return this._complete(this._onSuccessHandlers,h,a,"resolve")},Promise.prototype.reject=function(a){return this._complete(this._onErrorHandlers,f,a,"reject")},Promise.prototype._complete=function(a,b,c,f){if(this._status!=g){d.group("Promise already closed"),d.error("Attempted "+f+"() with ",c),d.error("Previous status = ",this._status,", previous value = ",this._value),d.trace(),this._completeTrace&&(d.error("Trace of previous completion:"),this._completeTrace.log(5)),d.groupEnd();return this}j&&(this._completeTrace=new e(new Error)),this._status=b,this._value=c,a.forEach(function(a){a.call(null,this._value)},this),this._onSuccessHandlers.length=0,this._onErrorHandlers.length=0,delete k[this._id],l.push(this);while(l.length>20)l.shift();return this},Promise.group=function(a){a instanceof Array||(a=Array.prototype.slice.call(arguments));if(a.length===0)return(new Promise).resolve([]);var b=new Promise,c=[],d=0,e=function(e){return function(g){c[e]=g,d++,b._status!==f&&d===a.length&&b.resolve(c)}};a.forEach(function(a,c){var d=e(c),f=b.reject.bind(b);a.then(d,f)});return b},b.Promise=Promise,b._outstanding=k,b._recent=l}),define("pilot/environment",function(a,b,c){function e(){return{settings:d}}var d=a("pilot/settings").settings;b.create=e}),define("ace/editor",function(a,b,c){a("pilot/fixoldbrowsers");var d=a("pilot/oop"),e=a("pilot/event"),f=a("pilot/lang"),g=a("pilot/useragent"),h=a("ace/keyboard/textinput").TextInput,i=a("ace/mouse_handler").MouseHandler,j=a("ace/keyboard/keybinding").KeyBinding,k=a("ace/edit_session").EditSession,l=a("ace/search").Search,m=a("ace/background_tokenizer").BackgroundTokenizer,n=a("ace/range").Range,o=a("pilot/event_emitter").EventEmitter,p=function(a,b){var c=a.getContainerElement();this.container=c,this.renderer=a,this.textInput=new h(a.getTextAreaContainer(),this),this.keyBinding=new j(this),g.isIPad||(this.$mouseHandler=new i(this)),this.$blockScrolling=0,this.$search=(new l).set({wrap:!0}),this.setSession(b||new k(""))};(function(){d.implement(this,o),this.$forwardEvents={gutterclick:1,gutterdblclick:1},this.$originalAddEventListener=this.addEventListener,this.$originalRemoveEventListener=this.removeEventListener,this.addEventListener=function(a,b){return this.$forwardEvents[a]?this.renderer.addEventListener(a,b):this.$originalAddEventListener(a,b)},this.removeEventListener=function(a,b){return this.$forwardEvents[a]?this.renderer.removeEventListener(a,b):this.$originalRemoveEventListener(a,b)},this.setKeyboardHandler=function(a){this.keyBinding.setKeyboardHandler(a)},this.getKeyboardHandler=function(){return this.keyBinding.getKeyboardHandler()},this.setSession=function(a){if(this.session!=a){if(this.session){var b=this.session;this.session.removeEventListener("change",this.$onDocumentChange),this.session.removeEventListener("changeMode",this.$onChangeMode),this.session.removeEventListener("changeTabSize",this.$onChangeTabSize),this.session.removeEventListener("changeWrapLimit",this.$onChangeWrapLimit),this.session.removeEventListener("changeWrapMode",this.$onChangeWrapMode),this.session.removeEventListener("changeFrontMarker",this.$onChangeFrontMarker),this.session.removeEventListener("changeBackMarker",this.$onChangeBackMarker),this.session.removeEventListener("changeBreakpoint",this.$onChangeBreakpoint),this.session.removeEventListener("changeAnnotation",this.$onChangeAnnotation),this.session.removeEventListener("changeOverwrite",this.$onCursorChange);var c=this.session.getSelection();c.removeEventListener("changeCursor",this.$onCursorChange),c.removeEventListener("changeSelection",this.$onSelectionChange),this.session.setScrollTopRow(this.renderer.getScrollTopRow())}this.session=a,this.$onDocumentChange=this.onDocumentChange.bind(this),a.addEventListener("change",this.$onDocumentChange),this.renderer.setSession(a),this.$onChangeMode=this.onChangeMode.bind(this),a.addEventListener("changeMode",this.$onChangeMode),this.$onChangeTabSize=this.renderer.updateText.bind(this.renderer),a.addEventListener("changeTabSize",this.$onChangeTabSize),this.$onChangeWrapLimit=this.onChangeWrapLimit.bind(this),a.addEventListener("changeWrapLimit",this.$onChangeWrapLimit),this.$onChangeWrapMode=this.onChangeWrapMode.bind(this),a.addEventListener("changeWrapMode",this.$onChangeWrapMode),this.$onChangeFrontMarker=this.onChangeFrontMarker.bind(this),this.session.addEventListener("changeFrontMarker",this.$onChangeFrontMarker),this.$onChangeBackMarker=this.onChangeBackMarker.bind(this),this.session.addEventListener("changeBackMarker",this.$onChangeBackMarker),this.$onChangeBreakpoint=this.onChangeBreakpoint.bind(this),this.session.addEventListener("changeBreakpoint",this.$onChangeBreakpoint),this.$onChangeAnnotation=this.onChangeAnnotation.bind(this),this.session.addEventListener("changeAnnotation",this.$onChangeAnnotation),this.$onCursorChange=this.onCursorChange.bind(this),this.session.addEventListener("changeOverwrite",this.$onCursorChange),this.selection=a.getSelection(),this.selection.addEventListener("changeCursor",this.$onCursorChange),this.$onSelectionChange=this.onSelectionChange.bind(this),this.selection.addEventListener("changeSelection",this.$onSelectionChange),this.onChangeMode(),this.bgTokenizer.setDocument(a.getDocument()),this.bgTokenizer.start(0),this.onCursorChange(),this.onSelectionChange(),this.onChangeFrontMarker(),this.onChangeBackMarker(),this.onChangeBreakpoint(),this.onChangeAnnotation(),this.renderer.scrollToRow(a.getScrollTopRow()),this.renderer.updateFull(),this._dispatchEvent("changeSession",{session:a,oldSession:b})}},this.getSession=function(){return this.session},this.getSelection=function(){return this.selection},this.resize=function(){this.renderer.onResize()},this.setTheme=function(a){this.renderer.setTheme(a)},this.setStyle=function(a){this.renderer.setStyle(a)},this.unsetStyle=function(a){this.renderer.unsetStyle(a)},this.$highlightBrackets=function(){this.session.$bracketHighlight&&(this.session.removeMarker(this.session.$bracketHighlight),this.session.$bracketHighlight=null);if(!this.$highlightPending){var a=this;this.$highlightPending=!0,setTimeout(function(){a.$highlightPending=!1;var b=a.session.findMatchingBracket(a.getCursorPosition());if(b){var c=new n(b.row,b.column,b.row,b.column+1);a.session.$bracketHighlight=a.session.addMarker(c,"ace_bracket")}},10)}},this.focus=function(){var a=this;g.isIE||setTimeout(function(){a.textInput.focus()}),this.textInput.focus()},this.blur=function(){this.textInput.blur()},this.onFocus=function(){this.renderer.showCursor(),this.renderer.visualizeFocus(),this._dispatchEvent("focus")},this.onBlur=function(){this.renderer.hideCursor(),this.renderer.visualizeBlur(),this._dispatchEvent("blur")},this.onDocumentChange=function(a){var b=a.data,c=b.range;this.bgTokenizer.start(c.start.row);if(c.start.row==c.end.row&&b.action!="insertLines"&&b.action!="removeLines")var d=c.end.row;else d=Infinity;this.renderer.updateLines(c.start.row,d),this.renderer.updateCursor()},this.onTokenizerUpdate=function(a){var b=a.data;this.renderer.updateLines(b.first,b.last)},this.onCursorChange=function(a){this.renderer.updateCursor(),this.$blockScrolling||this.renderer.scrollCursorIntoView(),this.renderer.moveTextAreaToCursor(this.textInput.getElement()),this.$highlightBrackets(),this.$updateHighlightActiveLine()},this.$updateHighlightActiveLine=function(){var a=this.getSession();a.$highlightLineMarker&&a.removeMarker(a.$highlightLineMarker),a.$highlightLineMarker=null;if(this.getHighlightActiveLine()&&(this.getSelectionStyle()!="line"||!this.selection.isMultiLine())){var b=this.getCursorPosition(),c=new n(b.row,0,b.row+1,0);a.$highlightLineMarker=a.addMarker(c,"ace_active_line","line")}},this.onSelectionChange=function(a){var b=this.getSession();b.$selectionMarker&&b.removeMarker(b.$selectionMarker),b.$selectionMarker=null;if(!this.selection.isEmpty()){var c=this.selection.getRange(),d=this.getSelectionStyle();b.$selectionMarker=b.addMarker(c,"ace_selection",d)}this.onCursorChange(a),this.$highlightSelectedWord&&this.mode.highlightSelection(this)},this.onChangeFrontMarker=function(){this.renderer.updateFrontMarkers()},this.onChangeBackMarker=function(){this.renderer.updateBackMarkers()},this.onChangeBreakpoint=function(){this.renderer.setBreakpoints(this.session.getBreakpoints())},this.onChangeAnnotation=function(){this.renderer.setAnnotations(this.session.getAnnotations())},this.onChangeMode=function(){var a=this.session.getMode();if(this.mode!=a){this.mode=a;var b=a.getTokenizer();if(!this.bgTokenizer){var c=this.onTokenizerUpdate.bind(this);this.bgTokenizer=new m(b,this),this.bgTokenizer.addEventListener("update",c)}else this.bgTokenizer.setTokenizer(b);this.renderer.setTokenizer(this.bgTokenizer)}},this.onChangeWrapLimit=function(){this.renderer.updateFull()},this.onChangeWrapMode=function(){this.renderer.onResize(!0)},this.getCopyText=function(){return this.selection.isEmpty()?"":this.session.getTextRange(this.getSelectionRange())},this.onCut=function(){this.$readOnly||this.selection.isEmpty()||(this.session.remove(this.getSelectionRange()),this.clearSelection())},this.insert=function(a){if(!this.$readOnly){var b=this.getCursorPosition();a=a.replace("\t",this.session.getTabString());if(!this.selection.isEmpty()){var b=this.session.remove(this.getSelectionRange());this.clearSelection()}else if(this.session.getOverwrite()){var c=new n.fromPoints(b,b);c.end.column+=a.length,this.session.remove(c)}this.clearSelection();var d=this.bgTokenizer.getState(b.row),e=this.mode.checkOutdent(d,this.session.getLine(b.row),a),f=this.session.getLine(b.row),g=this.mode.getNextLineIndent(d,f.slice(0,b.column),this.session.getTabString()),h=this.session.insert(b,a),d=this.bgTokenizer.getState(b.row);if(this.session.getDocument().isNewLine(a)){this.moveCursorTo(b.row+1,0);var i=this.session.getTabSize(),j=Number.MAX_VALUE;for(var k=b.row+1;k<=h.row;++k){var l=0;f=this.session.getLine(k);for(var m=0;m0;++m)f.charAt(m)=="\t"?o-=i:f.charAt(m)==" "&&(o-=1);this.session.remove(new n(k,0,k,m))}this.session.indentRows(b.row+1,h.row,g)}else e&&this.mode.autoOutdent(d,this.session,b.row)}},this.onTextInput=function(a){this.keyBinding.onTextInput(a)},this.onCommandKey=function(a,b,c){this.keyBinding.onCommandKey(a,b,c)},this.setOverwrite=function(a){this.session.setOverwrite()},this.getOverwrite=function(){return this.session.getOverwrite()},this.toggleOverwrite=function(){this.session.toggleOverwrite()},this.setScrollSpeed=function(a){this.$mouseHandler.setScrollSpeed(a)},this.getScrollSpeed=function(){return this.$mouseHandler.getScrollSpeed()},this.$selectionStyle="line",this.setSelectionStyle=function(a){this.$selectionStyle!=a&&(this.$selectionStyle=a,this.onSelectionChange(),this._dispatchEvent("changeSelectionStyle",{data:a}))},this.getSelectionStyle=function(){return this.$selectionStyle},this.$highlightActiveLine=!0,this.setHighlightActiveLine=function(a){this.$highlightActiveLine!=a&&(this.$highlightActiveLine=a,this.$updateHighlightActiveLine())},this.getHighlightActiveLine=function(){return this.$highlightActiveLine},this.$highlightSelectedWord=!0,this.setHighlightSelectedWord=function(a){this.$highlightSelectedWord!=a&&(this.$highlightSelectedWord=a,a?this.mode.highlightSelection(this):this.mode.clearSelectionHighlight(this))},this.getHighlightSelectedWord=function(){return this.$highlightSelectedWord},this.setShowInvisibles=function(a){this.getShowInvisibles()!=a&&this.renderer.setShowInvisibles(a)},this.getShowInvisibles=function(){return this.renderer.getShowInvisibles()},this.setShowPrintMargin=function(a){this.renderer.setShowPrintMargin(a)},this.getShowPrintMargin=function(){return this.renderer.getShowPrintMargin()},this.setPrintMarginColumn=function(a){this.renderer.setPrintMarginColumn(a)},this.getPrintMarginColumn=function(){return this.renderer.getPrintMarginColumn()},this.$readOnly=!1,this.setReadOnly=function(a){this.$readOnly=a},this.getReadOnly=function(){return this.$readOnly},this.removeRight=function(){this.$readOnly||(this.selection.isEmpty()&&this.selection.selectRight(),this.session.remove(this.getSelectionRange()),this.clearSelection())},this.removeLeft=function(){this.$readOnly||(this.selection.isEmpty()&&this.selection.selectLeft(),this.session.remove(this.getSelectionRange()),this.clearSelection())},this.removeWordRight=function(){this.$readOnly||(this.selection.isEmpty()&&this.selection.selectWordRight(),this.session.remove(this.getSelectionRange()),this.clearSelection())},this.removeWordLeft=function(){this.$readOnly||(this.selection.isEmpty()&&this.selection.selectWordLeft(),this.session.remove(this.getSelectionRange()),this.clearSelection())},this.removeToLineStart=function(){this.$readOnly||(this.selection.isEmpty()&&this.selection.selectLineStart(),this.session.remove(this.getSelectionRange()),this.clearSelection())},this.removeToLineEnd=function(){this.$readOnly||(this.selection.isEmpty()&&this.selection.selectLineEnd(),this.session.remove(this.getSelectionRange()),this.clearSelection())},this.splitLine=function(){if(!this.$readOnly){this.selection.isEmpty()||(this.session.remove(this.getSelectionRange()),this.clearSelection());var a=this.getCursorPosition();this.insert("\n"),this.moveCursorToPosition(a)}},this.transposeLetters=function(){if(!this.$readOnly){if(!this.selection.isEmpty())return;var a=this.getCursorPosition(),b=a.column;if(b==0)return;var c=this.session.getLine(a.row);if(b=this.getFirstVisibleRow()&&a<=this.getLastVisibleRow()},this.$getVisibleRowCount=function(){return this.renderer.getScrollBottomRow()-this.renderer.getScrollTopRow()+1},this.$getPageDownRow=function(){return this.renderer.getScrollBottomRow()},this.$getPageUpRow=function(){var a=this.renderer.getScrollTopRow(),b=this.renderer.getScrollBottomRow();return a-(b-a)},this.selectPageDown=function(){var a=this.$getPageDownRow()+Math.floor(this.$getVisibleRowCount()/2);this.scrollPageDown();var b=this.getSelection(),c=this.session.documentToScreenPosition(b.getSelectionLead()),d=this.session.screenToDocumentPosition(a,c.column);b.selectTo(d.row,d.column)},this.selectPageUp=function(){var a=this.renderer.getScrollTopRow()-this.renderer.getScrollBottomRow(),b=this.$getPageUpRow()+Math.round(a/2);this.scrollPageUp();var c=this.getSelection(),d=this.session.documentToScreenPosition(c.getSelectionLead()),e=this.session.screenToDocumentPosition(b,d.column);c.selectTo(e.row,e.column)},this.gotoPageDown=function(){var a=this.$getPageDownRow(),b=this.getCursorPositionScreen().column;this.scrollToRow(a),this.getSelection().moveCursorToScreen(a,b)},this.gotoPageUp=function(){var a=this.$getPageUpRow(),b=this.getCursorPositionScreen().column;this.scrollToRow(a),this.getSelection().moveCursorToScreen(a,b)},this.scrollPageDown=function(){this.scrollToRow(this.$getPageDownRow())},this.scrollPageUp=function(){this.renderer.scrollToRow(this.$getPageUpRow())},this.scrollToRow=function(a){this.renderer.scrollToRow(a)},this.scrollToLine=function(a,b){this.renderer.scrollToLine(a,b)},this.centerSelection=function(){var a=this.getSelectionRange(),b=Math.floor(a.start.row+(a.end.row-a.start.row)/2);this.renderer.scrollToLine(b,!0)},this.getCursorPosition=function(){return this.selection.getCursor()},this.getCursorPositionScreen=function(){return this.session.documentToScreenPosition(this.getCursorPosition())},this.getSelectionRange=function(){return this.selection.getRange()},this.selectAll=function(){this.$blockScrolling+=1,this.selection.selectAll(),this.$blockScrolling-=1},this.clearSelection=function(){this.selection.clearSelection()},this.moveCursorTo=function(a,b){this.selection.moveCursorTo(a,b)},this.moveCursorToPosition=function(a){this.selection.moveCursorToPosition(a)},this.gotoLine=function(a,b){this.selection.clearSelection(),this.$blockScrolling+=1,this.moveCursorTo(a-1,b||0),this.$blockScrolling-=1,this.isRowVisible(this.getCursorPosition().row)||this.scrollToLine(a,!0)},this.navigateTo=function(a,b){this.clearSelection(),this.moveCursorTo(a,b)},this.navigateUp=function(a){this.selection.clearSelection(),a=a||1,this.selection.moveCursorBy(-a,0)},this.navigateDown=function(a){this.selection.clearSelection(),a=a||1,this.selection.moveCursorBy(a,0)},this.navigateLeft=function(a){if(!this.selection.isEmpty()){var b=this.getSelectionRange().start;this.moveCursorToPosition(b)}else{a=a||1;while(a--)this.selection.moveCursorLeft()}this.clearSelection()},this.navigateRight=function(a){if(!this.selection.isEmpty()){var b=this.getSelectionRange().end;this.moveCursorToPosition(b)}else{a=a||1;while(a--)this.selection.moveCursorRight()}this.clearSelection()},this.navigateLineStart=function(){this.selection.moveCursorLineStart(),this.clearSelection()},this.navigateLineEnd=function(){this.selection.moveCursorLineEnd(),this.clearSelection()},this.navigateFileEnd=function(){this.selection.moveCursorFileEnd(),this.clearSelection()},this.navigateFileStart=function(){this.selection.moveCursorFileStart(),this.clearSelection()},this.navigateWordRight=function(){this.selection.moveCursorWordRight(),this.clearSelection()},this.navigateWordLeft=function(){this.selection.moveCursorWordLeft(),this.clearSelection()},this.replace=function(a,b){b&&this.$search.set(b);var c=this.$search.find(this.session);this.$tryReplace(c,a),c!==null&&this.selection.setSelectionRange(c)},this.replaceAll=function(a,b){b&&this.$search.set(b);var c=this.$search.findAll(this.session);if(!!c.length){var d=this.getSelectionRange();this.clearSelection(),this.selection.moveCursorTo(0,0),this.$blockScrolling+=1;for(var e=c.length-1;e>=0;--e)this.$tryReplace(c[e],a);this.selection.setSelectionRange(d),this.$blockScrolling-=1}},this.$tryReplace=function(a,b){var c=this.session.getTextRange(a),b=this.$search.replace(c,b);if(b!==null){a.end=this.session.replace(a,b);return a}return null},this.getLastSearchOptions=function(){return this.$search.getOptions()},this.find=function(a,b){this.clearSelection(),b=b||{},b.needle=a,this.$search.set(b),this.$find()},this.findNext=function(a){a=a||{},typeof a.backwards=="undefined"&&(a.backwards=!1),this.$search.set(a),this.$find()},this.findPrevious=function(a){a=a||{},typeof a.backwards=="undefined"&&(a.backwards=!0),this.$search.set(a),this.$find()},this.$find=function(a){this.selection.isEmpty()||this.$search.set({needle:this.session.getTextRange(this.getSelectionRange())}),typeof a!="undefined"&&this.$search.set({backwards:a});var b=this.$search.find(this.session);b&&(this.gotoLine(b.end.row+1,b.end.column),this.selection.setSelectionRange(b))},this.undo=function(){this.session.getUndoManager().undo()},this.redo=function(){this.session.getUndoManager().redo()}}).call(p.prototype),b.Editor=p}),define("pilot/event",function(a,b,c){function g(a,b,c){var f=0;e.isOpera&&e.isMac?f=0|(b.metaKey?1:0)|(b.altKey?2:0)|(b.shiftKey?4:0)|(b.ctrlKey?8:0):f=0|(b.ctrlKey?1:0)|(b.altKey?2:0)|(b.shiftKey?4:0)|(b.metaKey?8:0);if(c in d.MODIFIER_KEYS){switch(d.MODIFIER_KEYS[c]){case"Alt":f=2;break;case"Shift":f=4;break;case"Ctrl":f=1;break;default:f=8}c=0}f&8&&(c==91||c==93)&&(c=0);if(f==0&&!(c in d.FUNCTION_KEYS))return!1;return a(b,f,c)}var d=a("pilot/keys"),e=a("pilot/useragent"),f=a("pilot/dom");b.addListener=function(a,b,c){if(a.addEventListener)return a.addEventListener(b,c,!1);if(a.attachEvent){var d=function(){c(window.event)};c._wrapper=d,a.attachEvent("on"+b,d)}},b.removeListener=function(a,b,c){if(a.removeEventListener)return a.removeEventListener(b,c,!1);a.detachEvent&&a.detachEvent("on"+b,c._wrapper||c)},b.stopEvent=function(a){b.stopPropagation(a),b.preventDefault(a);return!1},b.stopPropagation=function(a){a.stopPropagation?a.stopPropagation():a.cancelBubble=!0},b.preventDefault=function(a){a.preventDefault?a.preventDefault():a.returnValue=!1},b.getDocumentX=function(a){return a.clientX?a.clientX+f.getPageScrollLeft():a.pageX},b.getDocumentY=function(a){return a.clientY?a.clientY+f.getPageScrollTop():a.pageY},b.getButton=function(a){if(a.type=="dblclick")return 0;if(a.type=="contextmenu")return 2;return a.preventDefault?a.button:({1:0,2:2,4:1})[a.button]},document.documentElement.setCapture?b.capture=function(a,c,d){function f(e){c&&c(e),d&&d(),b.removeListener(a,"mousemove",c),b.removeListener(a,"mouseup",f),b.removeListener(a,"losecapture",f),a.releaseCapture()}function e(a){c(a);return b.stopPropagation(a)}b.addListener(a,"mousemove",c),b.addListener(a,"mouseup",f),b.addListener(a,"losecapture",f),a.setCapture()}:b.capture=function(a,b,c){function e(a){b&&b(a),c&&c(),document.removeEventListener("mousemove",d,!0),document.removeEventListener("mouseup",e,!0),a.stopPropagation()}function d(a){b(a),a.stopPropagation()}document.addEventListener("mousemove",d,!0),document.addEventListener("mouseup",e,!0)},b.addMouseWheelListener=function(a,c){var d=function(a){a.wheelDelta!==undefined?a.wheelDeltaX!==undefined?(a.wheelX=-a.wheelDeltaX/8,a.wheelY=-a.wheelDeltaY/8):(a.wheelX=0,a.wheelY=-a.wheelDelta/8):a.axis&&a.axis==a.HORIZONTAL_AXIS?(a.wheelX=(a.detail||0)*5,a.wheelY=0):(a.wheelX=0,a.wheelY=(a.detail||0)*5),c(a)};b.addListener(a,"DOMMouseScroll",d),b.addListener(a,"mousewheel",d)},b.addMultiMouseDownListener=function(a,c,d,f,g){var h=0,i,j,k=function(a){h+=1,h==1&&(i=a.clientX,j=a.clientY,setTimeout(function(){h=0},f||600));if(b.getButton(a)!=c||Math.abs(a.clientX-i)>5||Math.abs(a.clientY-j)>5)h=0;h==d&&(h=0,g(a));return b.preventDefault(a)};b.addListener(a,"mousedown",k),e.isIE&&b.addListener(a,"dblclick",k)},b.addCommandKeyListener=function(a,c){var d=b.addListener;if(e.isOldGecko){var f=null;d(a,"keydown",function(a){f=a.keyCode}),d(a,"keypress",function(a){return g(c,a,f)})}else{var h=null;d(a,"keydown",function(a){h=a.keyIdentifier||a.keyCode;return g(c,a,a.keyCode)}),e.isMac&&e.isOpera&&d(a,"keypress",function(a){var b=a.keyIdentifier||a.keyCode;if(h!==b)return g(c,a,a.keyCode);h=null})}}}),define("pilot/dom",function(a,b,c){var d="http://www.w3.org/1999/xhtml";b.createElement=function(a,b){return document.createElementNS?document.createElementNS(b||d,a):document.createElement(a)},b.setText=function(a,b){a.innerText!==undefined&&(a.innerText=b),a.textContent!==undefined&&(a.textContent=b)},document.documentElement.classList?(b.hasCssClass=function(a,b){return a.classList.contains(b)},b.addCssClass=function(a,b){a.classList.add(b)},b.removeCssClass=function(a,b){a.classList.remove(b)},b.toggleCssClass=function(a,b){return a.classList.toggle(b)}):(b.hasCssClass=function(a,b){var c=a.className.split(/\s+/g);return c.indexOf(b)!==-1},b.addCssClass=function(a,c){b.hasCssClass(a,c)||(a.className+=" "+c)},b.removeCssClass=function(a,b){var c=a.className.split(/\s+/g);while(!0){var d=c.indexOf(b);if(d==-1)break;c.splice(d,1)}a.className=c.join(" ")},b.toggleCssClass=function(a,b){var c=a.className.split(/\s+/g),d=!0;while(!0){var e=c.indexOf(b);if(e==-1)break;d=!1,c.splice(e,1)}d&&c.push(b),a.className=c.join(" ");return d}),b.setCssClass=function(a,c,d){d?b.addCssClass(a,c):b.removeCssClass(a,c)},b.importCssString=function(a,b){b=b||document;if(b.createStyleSheet){var c=b.createStyleSheet();c.cssText=a}else{var e=b.createElementNS?b.createElementNS(d,"style"):b.createElement("style");e.appendChild(b.createTextNode(a));var f=b.getElementsByTagName("head")[0]||b.documentElement;f.appendChild(e)}},b.getInnerWidth=function(a){return parseInt(b.computedStyle(a,"paddingLeft"))+parseInt(b.computedStyle(a,"paddingRight"))+a.clientWidth},b.getInnerHeight=function(a){return parseInt(b.computedStyle(a,"paddingTop"))+parseInt(b.computedStyle(a,"paddingBottom"))+a.clientHeight},window.pageYOffset!==undefined?(b.getPageScrollTop=function(){return window.pageYOffset},b.getPageScrollLeft=function(){return window.pageXOffset}):(b.getPageScrollTop=function(){return document.body.scrollTop},b.getPageScrollLeft=function(){return document.body.scrollLeft}),b.computedStyle=function(a,b){return window.getComputedStyle?(window.getComputedStyle(a,"")||{})[b]||"":a.currentStyle[b]},b.scrollbarWidth=function(){var a=b.createElement("p");a.style.width="100%",a.style.height="200px";var c=b.createElement("div"),d=c.style;d.position="absolute",d.left="-10000px",d.overflow="hidden",d.width="200px",d.height="150px",c.appendChild(a);var e=document.body||document.documentElement;e.appendChild(c);var f=a.offsetWidth;d.overflow="scroll";var g=a.offsetWidth;f==g&&(g=c.clientWidth),e.removeChild(c);return f-g},b.setInnerHtml=function(a,b){var c=a.cloneNode(!1);c.innerHTML=b,a.parentNode.replaceChild(c,a);return c},b.setInnerText=function(a,b){document.body&&"textContent"in document.body?a.textContent=b:a.innerText=b},b.getInnerText=function(a){return document.body&&"textContent"in document.body?a.textContent:a.innerText||a.textContent||""},b.getParentWindow=function(a){return a.defaultView||a.parentWindow},b.getSelectionStart=function(a){var b;try{b=a.selectionStart||0}catch(c){b=0}return b},b.setSelectionStart=function(a,b){return a.selectionStart=b},b.getSelectionEnd=function(a){var b;try{b=a.selectionEnd||0}catch(c){b=0}return b},b.setSelectionEnd=function(a,b){return a.selectionEnd=b}}),define("ace/keyboard/textinput",function(a,b,c){var d=a("pilot/event"),e=a("pilot/useragent"),f=a("pilot/dom"),g=function(a,b){function k(a){if(!i){var d=a||c.value;d&&(d.charCodeAt(d.length-1)==g.charCodeAt(0)?(d=d.slice(0,-1),d&&b.onTextInput(d)):b.onTextInput(d))}i=!1,c.value=g,c.select()}var c=f.createElement("textarea");c.style.left="-10000px",a.appendChild(c);var g=String.fromCharCode(0);k();var h=!1,i=!1,j="",l=function(a){e.isIE&&c.value.charCodeAt(0)>128||setTimeout(function(){h||k()},0)},m=function(a){h=!0,e.isIE||(k(),c.value=""),b.onCompositionStart(),e.isGecko||setTimeout(n,0)},n=function(){!h||b.onCompositionUpdate(c.value)},o=function(){h=!1,b.onCompositionEnd(),setTimeout(function(){k()},0)},p=function(a){i=!0;var d=b.getCopyText();d?c.value=d:a.preventDefault(),c.select(),setTimeout(function(){k()},0)},q=function(a){i=!0;var d=b.getCopyText();d?(c.value=d,b.onCut()):a.preventDefault(),c.select(),setTimeout(function(){k()},0)};d.addCommandKeyListener(c,b.onCommandKey.bind(b)),d.addListener(c,"keypress",l);if(e.isIE){var r={13:1,27:1};d.addListener(c,"keyup",function(a){h&&(!c.value||r[a.keyCode])&&setTimeout(o,0);(c.value.charCodeAt(0)|0)<129||(h?n():m())})}d.addListener(c,"textInput",l),d.addListener(c,"paste",function(a){a.clipboardData&&a.clipboardData.getData?(k(a.clipboardData.getData("text/plain")),a.preventDefault()):l()}),e.isIE||d.addListener(c,"propertychange",l),e.isIE?(d.addListener(c,"beforecopy",function(a){var c=b.getCopyText();c?clipboardData.setData("Text",c):a.preventDefault()}),d.addListener(a,"keydown",function(a){if(a.ctrlKey&&a.keyCode==88){var c=b.getCopyText();c&&(clipboardData.setData("Text",c),b.onCut()),d.preventDefault(a)}})):(d.addListener(c,"copy",p),d.addListener(c,"cut",q)),d.addListener(c,"compositionstart",m),e.isGecko&&d.addListener(c,"text",n),e.isWebKit&&d.addListener(c,"keyup",n),d.addListener(c,"compositionend",o),d.addListener(c,"blur",function(){b.onBlur()}),d.addListener(c,"focus",function(){b.onFocus(),c.select()}),this.focus=function(){b.onFocus(),c.select(),c.focus()},this.blur=function(){c.blur()},this.getElement=function(){return c},this.onContextMenu=function(a,b){a&&(j||(j=c.style.cssText),c.style.cssText="position:fixed; z-index:1000;left:"+(a.x-2)+"px; top:"+(a.y-2)+"px;"),b&&(c.value="")},this.onContextMenuClose=function(){setTimeout(function(){j&&(c.style.cssText=j,j=""),k()},0)}};b.TextInput=g}),define("ace/mouse_handler",function(a,b,c){var d=a("pilot/event"),e=a("pilot/dom"),f=0,g=1,h=2,i=250,j=5,k=function(a){this.editor=a,d.addListener(a.container,"mousedown",function(b){a.focus();return d.preventDefault(b)}),d.addListener(a.container,"selectstart",function(a){return d.preventDefault(a)});var b=a.renderer.getMouseEventTarget();d.addListener(b,"mousedown",this.onMouseDown.bind(this)),d.addMultiMouseDownListener(b,0,2,500,this.onMouseDoubleClick.bind(this)),d.addMultiMouseDownListener(b,0,3,600,this.onMouseTripleClick.bind(this)),d.addMultiMouseDownListener(b,0,4,600,this.onMouseQuadClick.bind(this)),d.addMouseWheelListener(b,this.onMouseWheel.bind(this))};(function(){this.$scrollSpeed=1,this.setScrollSpeed=function(a){this.$scrollSpeed=a},this.getScrollSpeed=function(){return this.$scrollSpeed},this.$getEventPosition=function(a){var b=d.getDocumentX(a),c=d.getDocumentY(a),e=this.editor.renderer.screenToTextCoordinates(b,c);e.row=Math.max(0,Math.min(e.row,this.editor.session.getLength()-1));return e},this.$distance=function(a,b,c,d){return Math.sqrt(Math.pow(c-a,2)+Math.pow(d-b,2))},this.onMouseDown=function(a){function C(b){a.shiftKey?l.selection.selectToPosition(b):m.$clickSelection||(l.moveCursorToPosition(b),l.selection.clearSelection(b.row,b.column)),p=g}var b=d.getDocumentX(a),c=d.getDocumentY(a),k=this.$getEventPosition(a),l=this.editor,m=this,n=l.getSelectionRange(),o=n.isEmpty(),p=f,q=!1,r=d.getButton(a);if(r!==0)o&&l.moveCursorToPosition(k),r==2&&(l.textInput.onContextMenu({x:b,y:c},o),d.capture(l.container,function(){},l.textInput.onContextMenuClose));else{q=!l.getReadOnly()&&!o&&n.contains(k.row,k.column),q||C(k),l.renderer.scrollCursorIntoView();var s,t,u=l.getOverwrite(),v=(new Date).getTime(),w,x,y=function(a){s=d.getDocumentX(a),t=d.getDocumentY(a)},z=function(){clearInterval(F),p==f?C(k):p==h&&A(),m.$clickSelection=null,p=f},A=function(){e.removeCssClass(l.container,"ace_dragging"),l.session.removeMarker(dragSelectionMarker),m.$clickSelection||w||(l.moveCursorToPosition(k),l.selection.clearSelection(k.row,k.column));if(!!w){if(x.contains(w.row,w.column)){w=null;return}l.clearSelection();var a=l.moveText(x,w);if(!a){w=null;return}l.selection.setSelectionRange(a)}},B=function(){if(s!==undefined&&t!==undefined){if(p==f){var a=m.$distance(b,c,s,t),d=(new Date).getTime();if(a>j){p=g;var k=l.renderer.screenToTextCoordinates(s,t);k.row=Math.max(0,Math.min(k.row,l.session.getLength()-1)),C(k)}else if(d-v>i){p=h,x=l.getSelectionRange();var n=l.getSelectionStyle();dragSelectionMarker=l.session.addMarker(x,"ace_selection",n),l.clearSelection(),e.addCssClass(l.container,"ace_dragging")}}p==h?E():p==g&&D()}},D=function(){var a=l.renderer.screenToTextCoordinates(s,t);a.row=Math.max(0,Math.min(a.row,l.session.getLength()-1));if(m.$clickSelection)if(m.$clickSelection.contains(a.row,a.column))l.selection.setSelectionRange(m.$clickSelection);else{if(m.$clickSelection.compare(a.row,a.column)==-1)var b=m.$clickSelection.end;else var b=m.$clickSelection.start;l.selection.setSelectionAnchor(b.row,b.column),l.selection.selectToPosition(a)}else l.selection.selectToPosition(a);l.renderer.scrollCursorIntoView()},E=function(){w=l.renderer.screenToTextCoordinates(s,t),w.row=Math.max(0,Math.min(w.row,l.session.getLength()-1)),l.moveCursorToPosition(w)};d.capture(l.container,y,z);var F=setInterval(B,20);return d.preventDefault(a)}},this.onMouseDoubleClick=function(a){var b=this.$getEventPosition(a);this.editor.moveCursorToPosition(b),this.editor.selection.selectWord(),this.$clickSelection=this.editor.getSelectionRange()},this.onMouseTripleClick=function(a){var b=this.$getEventPosition(a);this.editor.moveCursorToPosition(b),this.editor.selection.selectLine(),this.$clickSelection=this.editor.getSelectionRange()},this.onMouseQuadClick=function(a){this.editor.selectAll(),this.$clickSelection=this.editor.getSelectionRange()},this.onMouseWheel=function(a){var b=this.$scrollSpeed*2;this.editor.renderer.scrollBy(a.wheelX*b,a.wheelY*b);return d.preventDefault(a)}}).call(k.prototype),b.MouseHandler=k}),define("ace/keyboard/keybinding",function(a,b,c){var d=a("pilot/useragent"),e=a("pilot/keys"),f=a("pilot/event"),g=a("pilot/settings").settings,h=a("pilot/canon");a("ace/commands/default_commands");var i=function(a){this.$editor=a,this.$data={},this.$keyboardHandler=null};(function(){this.setKeyboardHandler=function(a){this.$keyboardHandler!=a&&(this.$data={},this.$keyboardHandler=a)},this.getKeyboardHandler=function(){return this.$keyboardHandler},this.$callKeyboardHandler=function(a,b,c,d){var e={editor:this.$editor},g;this.$keyboardHandler&&(g=this.$keyboardHandler.handleKeyboard(this.$data,b,c,d,a));if(!g||!g.command)b!=0||d!=0?g={command:h.findKeyCommand(e,"editor",b,c)}:g={command:"inserttext",args:{text:c}};if(g){var i=h.exec(g.command,e,"editor",g.args);if(i)return f.stopEvent(a)}},this.onCommandKey=function(a,b,c){var d=e.keyCodeToString(c);this.$callKeyboardHandler(a,b,d,c)},this.onTextInput=function(a){this.$callKeyboardHandler({},0,a,0)}}).call(i.prototype),b.KeyBinding=i}),define("ace/commands/default_commands",function(a,b,c){function f(a,b){return{win:a,mac:b,sender:"editor"}}var d=a("pilot/lang"),e=a("pilot/canon");e.addCommand({name:"null",exec:function(a,b,c){}}),e.addCommand({name:"selectall",bindKey:f("Ctrl-A","Command-A"),exec:function(a,b,c){a.editor.selectAll()}}),e.addCommand({name:"removeline",bindKey:f("Ctrl-D","Command-D"),exec:function(a,b,c){a.editor.removeLines()}}),e.addCommand({name:"gotoline",bindKey:f("Ctrl-L","Command-L"),exec:function(a,b,c){var d=parseInt(prompt("Enter line number:"));isNaN(d)||a.editor.gotoLine(d)}}),e.addCommand({name:"togglecomment",bindKey:f("Ctrl-7","Command-7"),exec:function(a,b,c){a.editor.toggleCommentLines()}}),e.addCommand({name:"findnext",bindKey:f("Ctrl-K","Command-G"),exec:function(a,b,c){a.editor.findNext()}}),e.addCommand({name:"findprevious",bindKey:f("Ctrl-Shift-K","Command-Shift-G"),exec:function(a,b,c){a.editor.findPrevious()}}),e.addCommand({name:"find",bindKey:f("Ctrl-F","Command-F"),exec:function(a,b,c){var d=prompt("Find:");a.editor.find(d)}}),e.addCommand({name:"replace",bindKey:f("Ctrl-R","Command-Option-F"),exec:function(a,b,c){var d=prompt("Find:");if(!!d){var e=prompt("Replacement:");if(!e)return;a.editor.replace(e,{needle:d})}}}),e.addCommand({name:"replaceall",bindKey:f("Ctrl-Shift-R","Command-Shift-Option-F"),exec:function(a,b,c){var d=prompt("Find:");if(!!d){var e=prompt("Replacement:");if(!e)return;a.editor.replaceAll(e,{needle:d})}}}),e.addCommand({name:"undo",bindKey:f("Ctrl-Z","Command-Z"),exec:function(a,b,c){a.editor.undo()}}),e.addCommand({name:"redo",bindKey:f("Ctrl-Shift-Z|Ctrl-Y","Command-Shift-Z|Command-Y"),exec:function(a,b,c){a.editor.redo()}}),e.addCommand({name:"overwrite",bindKey:f("Insert","Insert"),exec:function(a,b,c){a.editor.toggleOverwrite()}}),e.addCommand({name:"copylinesup",bindKey:f("Ctrl-Alt-Up","Command-Option-Up"),exec:function(a,b,c){a.editor.copyLinesUp()}}),e.addCommand({name:"movelinesup",bindKey:f("Alt-Up","Option-Up"),exec:function(a,b,c){a.editor.moveLinesUp()}}),e.addCommand({name:"selecttostart",bindKey:f("Alt-Shift-Up","Command-Shift-Up"),exec:function(a,b,c){a.editor.getSelection().selectFileStart()}}),e.addCommand({name:"gotostart",bindKey:f("Ctrl-Home|Ctrl-Up","Command-Home|Command-Up"),exec:function(a,b,c){a.editor.navigateFileStart()}}),e.addCommand({name:"selectup",bindKey:f("Shift-Up","Shift-Up"),exec:function(a,b,c){a.editor.getSelection().selectUp()}}),e.addCommand({name:"golineup",bindKey:f("Up","Up|Ctrl-P"),exec:function(a,b,c){a.editor.navigateUp(b.times)}}),e.addCommand({name:"copylinesdown",bindKey:f("Ctrl-Alt-Down","Command-Option-Down"),exec:function(a,b,c){a.editor.copyLinesDown()}}),e.addCommand({name:"movelinesdown",bindKey:f("Alt-Down","Option-Down"),exec:function(a,b,c){a.editor.moveLinesDown()}}),e.addCommand({name:"selecttoend",bindKey:f("Alt-Shift-Down","Command-Shift-Down"),exec:function(a,b,c){a.editor.getSelection().selectFileEnd()}}),e.addCommand({name:"gotoend",bindKey:f("Ctrl-End|Ctrl-Down","Command-End|Command-Down"),exec:function(a,b,c){a.editor.navigateFileEnd()}}),e.addCommand({name:"selectdown",bindKey:f("Shift-Down","Shift-Down"),exec:function(a,b,c){a.editor.getSelection().selectDown()}}),e.addCommand({name:"golinedown",bindKey:f("Down","Down|Ctrl-N"),exec:function(a,b,c){a.editor.navigateDown(b.times)}}),e.addCommand({name:"selectwordleft",bindKey:f("Ctrl-Shift-Left","Option-Shift-Left"),exec:function(a,b,c){a.editor.getSelection().selectWordLeft()}}),e.addCommand({name:"gotowordleft",bindKey:f("Ctrl-Left","Option-Left"),exec:function(a,b,c){a.editor.navigateWordLeft()}}),e.addCommand({name:"selecttolinestart",bindKey:f("Alt-Shift-Left","Command-Shift-Left"),exec:function(a,b,c){a.editor.getSelection().selectLineStart()}}),e.addCommand({name:"gotolinestart",bindKey:f("Alt-Left|Home","Command-Left|Home|Ctrl-A"),exec:function(a,b,c){a.editor.navigateLineStart()}}),e.addCommand({name:"selectleft",bindKey:f("Shift-Left","Shift-Left"),exec:function(a,b,c){a.editor.getSelection().selectLeft()}}),e.addCommand({name:"gotoleft",bindKey:f("Left","Left|Ctrl-B"),exec:function(a,b,c){a.editor.navigateLeft(b.times)}}),e.addCommand({name:"selectwordright",bindKey:f("Ctrl-Shift-Right","Option-Shift-Right"),exec:function(a,b,c){a.editor.getSelection().selectWordRight()}}),e.addCommand({name:"gotowordright",bindKey:f("Ctrl-Right","Option-Right"),exec:function(a,b,c){a.editor.navigateWordRight()}}),e.addCommand({name:"selecttolineend",bindKey:f("Alt-Shift-Right","Command-Shift-Right"),exec:function(a,b,c){a.editor.getSelection().selectLineEnd()}}),e.addCommand({name:"gotolineend",bindKey:f("Alt-Right|End","Command-Right|End|Ctrl-E"),exec:function(a,b,c){a.editor.navigateLineEnd()}}),e.addCommand({name:"selectright",bindKey:f("Shift-Right","Shift-Right"),exec:function(a,b,c){a.editor.getSelection().selectRight()}}),e.addCommand({name:"gotoright",bindKey:f("Right","Right|Ctrl-F"),exec:function(a,b,c){a.editor.navigateRight(b.times)}}),e.addCommand({name:"selectpagedown",bindKey:f("Shift-PageDown","Shift-PageDown"),exec:function(a,b,c){a.editor.selectPageDown()}}),e.addCommand({name:"pagedown",bindKey:f(null,"PageDown"),exec:function(a,b,c){a.editor.scrollPageDown()}}),e.addCommand({name:"gotopagedown",bindKey:f("PageDown","Option-PageDown|Ctrl-V"),exec:function(a,b,c){a.editor.gotoPageDown()}}),e.addCommand({name:"selectpageup",bindKey:f("Shift-PageUp","Shift-PageUp"),exec:function(a,b,c){a.editor.selectPageUp()}}),e.addCommand({name:"pageup",bindKey:f(null,"PageUp"),exec:function(a,b,c){a.editor.scrollPageUp()}}),e.addCommand({name:"gotopageup",bindKey:f("PageUp","Option-PageUp"),exec:function(a,b,c){a.editor.gotoPageUp()}}),e.addCommand({name:"selectlinestart",bindKey:f("Shift-Home","Shift-Home"),exec:function(a,b,c){a.editor.getSelection().selectLineStart()}}),e.addCommand({name:"selectlineend",bindKey:f("Shift-End","Shift-End"),exec:function(a,b,c){a.editor.getSelection().selectLineEnd()}}),e.addCommand({name:"del",bindKey:f("Delete","Delete|Ctrl-D"),exec:function(a,b,c){a.editor.removeRight()}}),e.addCommand({name:"backspace",bindKey:f("Ctrl-Backspace|Command-Backspace|Option-Backspace|Shift-Backspace|Backspace","Ctrl-Backspace|Command-Backspace|Shift-Backspace|Backspace|Ctrl-H"),exec:function(a,b,c){a.editor.removeLeft()}}),e.addCommand({name:"removetolinestart",bindKey:f(null,"Option-Backspace"),exec:function(a,b,c){a.editor.removeToLineStart()}}),e.addCommand({name:"removetolineend",bindKey:f(null,"Ctrl-K"),exec:function(a,b,c){a.editor.removeToLineEnd()}}),e.addCommand({name:"removewordleft",bindKey:f(null,"Alt-Backspace|Ctrl-Alt-Backspace"),exec:function(a,b,c){a.editor.removeWordLeft()}}),e.addCommand({name:"removewordright",bindKey:f(null,"Alt-Delete"),exec:function(a,b,c){a.editor.removeWordRight()}}),e.addCommand({name:"outdent",bindKey:f("Shift-Tab","Shift-Tab"),exec:function(a,b,c){a.editor.blockOutdent()}}),e.addCommand({name:"indent",bindKey:f("Tab","Tab"),exec:function(a,b,c){a.editor.indent()}}),e.addCommand({name:"inserttext",exec:function(a,b,c){a.editor.insert(d.stringRepeat(b.text||"",b.times||1))}}),e.addCommand({name:"splitline",bindKey:f(null,"Ctrl-O"),exec:function(a,b,c){a.editor.splitLine()}}),e.addCommand({name:"transposeletters",bindKey:f("Ctrl-T","Ctrl-T"),exec:function(a,b,c){a.editor.transposeLetters()}})}),define("ace/edit_session",function(a,b,c){var d=a("pilot/oop"),e=a("pilot/lang"),f=a("pilot/event_emitter").EventEmitter,g=a("ace/selection").Selection,h=a("ace/mode/text").Mode,j=a("ace/range").Range,k=a("ace/document").Document,l=function(a,b){this.$modified=!0,this.$breakpoints=[],this.$frontMarkers={},this.$backMarkers={},this.$markerId=1,this.$wrapData=[],a instanceof k?this.setDocument(a):this.setDocument(new k(a)),this.selection=new g(this),b&&this.setMode(b)};(function(){d.implement(this,f),this.setDocument=function(a){if(this.doc)throw new Error("Document is already set");this.doc=a,a.on("change",this.onChange.bind(this))},this.getDocument=function(){return this.doc},this.onChange=function(a){var b=a.data;this.$modified=!0,!this.$fromUndo&&this.$undoManager&&!b.ignore&&(this.$deltas.push(b),this.$informUndoManager.schedule()),this.$updateWrapDataOnChange(a),this._dispatchEvent("change",a)},this.setValue=function(a){this.doc.setValue(a),this.$deltas=[],this.getUndoManager().reset()},this.getValue=this.toString=function(){return this.doc.getValue()},this.getSelection=function(){return this.selection},this.setUndoManager=function(a){this.$undoManager=a,this.$deltas=[],this.$informUndoManager&&this.$informUndoManager.cancel();if(a){var b=this;this.$informUndoManager=e.deferredCall(function(){b.$deltas.length>0&&a.execute({action:"aceupdate",args:[b.$deltas,b]}),b.$deltas=[]})}},this.$defaultUndoManager={undo:function(){},redo:function(){},reset:function(){}},this.getUndoManager=function(){return this.$undoManager||this.$defaultUndoManager},this.getTabString=function(){return this.getUseSoftTabs()?e.stringRepeat(" ",this.getTabSize()):"\t"},this.$useSoftTabs=!0,this.setUseSoftTabs=function(a){this.$useSoftTabs!==a&&(this.$useSoftTabs=a)},this.getUseSoftTabs=function(){return this.$useSoftTabs},this.$tabSize=4,this.setTabSize=function(a){!isNaN(a)&&this.$tabSize!==a&&(this.$modified=!0,this.$tabSize=a,this._dispatchEvent("changeTabSize"))},this.getTabSize=function(){return this.$tabSize},this.isTabStop=function(a){return this.$useSoftTabs&&a.column%this.$tabSize==0},this.$overwrite=!1,this.setOverwrite=function(a){this.$overwrite!=a&&(this.$overwrite=a,this._dispatchEvent("changeOverwrite"))},this.getOverwrite=function(){return this.$overwrite},this.toggleOverwrite=function(){this.setOverwrite(!this.$overwrite)},this.getBreakpoints=function(){return this.$breakpoints},this.setBreakpoints=function(a){this.$breakpoints=[];for(var b=0;b0&&(d=!!c.charAt(b-1).match(this.tokenRe)),d||(d=!!c.charAt(b).match(this.tokenRe));var e=d?this.tokenRe:this.nonTokenRe,f=b;if(f>0){do f--;while(f>=0&&c.charAt(f).match(e));f++}var g=b;while(g=0){var h=g.charAt(d);if(h==c){f-=1;if(f==0)return{row:e,column:d}}else h==a&&(f+=1);d-=1}e-=1;if(e<0)break;var g=this.getLine(e),d=g.length-1}return null},this.$findClosingBracket=function(a,b){var c=this.$brackets[a],d=b.column,e=b.row,f=1,g=this.getLine(e),h=this.getLength();while(!0){while(d=h)break;var g=this.getLine(e),d=0}return null},this.insert=function(a,b){return this.doc.insert(a,b)},this.remove=function(a){return this.doc.remove(a)},this.undoChanges=function(a){!a.length||(this.$fromUndo=!0,this.doc.revertDeltas(a),this.$fromUndo=!1,this.$setUndoSelection(a,!0))},this.redoChanges=function(a){!a.length||(this.$fromUndo=!0,this.doc.applyDeltas(a),this.$fromUndo=!1,this.$setUndoSelection(a,!1))},this.$setUndoSelection=function(a,b){b&&(a=a.map(function(a){var b={range:a.range};a.action=="insertText"||a.action=="insertLines"?b.action="removeText":b.action="insertText";return b}).reverse());var c=[{}];for(var d=0;d=this.doc.getLength()-1)return 0;var c=this.doc.removeLines(a,b);this.doc.insertLines(a+1,c);return 1},this.duplicateLines=function(a,b){var a=this.$clipRowToDocument(a),b=this.$clipRowToDocument(b),c=this.getLines(a,b);this.doc.insertLines(a,c);var d=b-a+1;return d},this.$clipRowToDocument=function(a){return Math.max(0,Math.min(a,this.doc.getLength()-1))},this.$wrapLimit=80,this.$useWrapMode=!1,this.$wrapLimitRange={min:null,max:null},this.setUseWrapMode=function(a){if(a!=this.$useWrapMode){this.$useWrapMode=a,this.$modified=!0;if(a){var b=this.getLength();this.$wrapData=[];for(i=0;i0){this.$wrapLimit=b,this.$modified=!0,this.$useWrapMode&&(this.$updateWrapData(0,this.getLength()-1),this._dispatchEvent("changeWrapLimit"));return!0}return!1},this.$constrainWrapLimit=function(a){var b=this.$wrapLimitRange.min;b&&(a=Math.max(b,a));var c=this.$wrapLimitRange.max;c&&(a=Math.min(c,a));return Math.max(1,a)},this.getWrapLimit=function(){return this.$wrapLimit},this.getWrapLimitRange=function(){return{min:this.$wrapLimitRange.min,max:this.$wrapLimitRange.max}},this.$updateWrapDataOnChange=function(a){if(!!this.$useWrapMode){var b,c=a.data.action,d=a.data.range.start.row,e=a.data.range.end.row;c.indexOf("Lines")!=-1?(c=="insertLines"?e=d+a.data.lines.length:e=d,b=a.data.lines.length):b=e-d;if(b!=0)if(c.indexOf("remove")!=-1)this.$wrapData.splice(d,b),e=d;else{var f=[d,0];for(var g=0;gb){var k=h+b;if(e[k]>=g){while(e[k]>=g)k++;j(k)}else{for(k;k!=h-1;k--)if(e[k]>=g){k++;break}k>h?j(k):j(h+b)}}return d},this.$getDisplayTokens=function(a){var d=[],e=this.getTabSize();for(var f=0;f=12352&&h<=12447||h>=12448&&h<=12543||h>=19968&&h<=40959||h>=63744&&h<=64255||h>=13312&&h<=19903?d.push(b,c):d.push(b)}return d},this.$getStringScreenWidth=function(a){var b=0,c=this.getTabSize();for(var d=0;d=12352&&e<=12447||e>=12448&&e<=12543||e>=19968&&e<=40959||e>=63744&&e<=64255||e>=13312&&e<=19903?b+=2:b+=1}return b},this.getRowHeight=function(a,b){var c;!this.$useWrapMode||!this.$wrapData[b]?c=1:c=this.$wrapData[b].length+1;return c*a.lineHeight},this.getScreenLastRowColumn=function(a,b){if(!this.$useWrapMode)return this.$getStringScreenWidth(this.getLine(a));var c=this.$screenToDocumentRow(a),d=c[0],e=c[1],f,g;this.$wrapData[d][e]?(f=this.$wrapData[d][e-1]||0,g=this.$wrapData[d][e],b&&g--):(g=this.getLine(d).length,f=this.$wrapData[d][e-1]||0);return b?g:this.$getStringScreenWidth(this.getLine(d).substring(f,g))},this.getDocumentLastRowColumn=function(a,b){if(!this.$useWrapMode)return this.getLine(a).length;var c=this.documentToScreenRow(a,b);return this.getScreenLastRowColumn(c,!0)},this.getScreenFirstRowColumn=function(a){if(!this.$useWrapMode)return 0;var b=this.$screenToDocumentRow(a),c=b[0],d=b[1];return this.$wrapData[c][d-1]||0},this.getRowSplitData=function(a){return this.$useWrapMode?this.$wrapData[a]:undefined},this.$screenToDocumentRow=function(a){if(!this.$useWrapMode)return[a,0];var b=this.$wrapData,c=this.getLength(),d=0;while(d=b[d].length+1)a-=b[d].length+1,d++;return[d,a]},this.screenToDocumentRow=function(a){return this.$screenToDocumentRow(a)[0]},this.screenToDocumentColumn=function(a,b){return this.screenToDocumentPosition(a,b).column},this.screenToDocumentPosition=function(a,b){var c,d,e,f=b,g=this.getLength();if(!this.$useWrapMode)d=a>=g?g-1:a<0?0:a,a=0,e=0,c=this.getLine(d);else{var h=this.$wrapData,d=0;while(d=h[d].length+1)a-=h[d].length+1,d++;d>=g&&(d=g-1,a=h[d].length),e=h[d][a-1]||0,c=this.getLine(d).substring(e)}var i=this.getTabSize();for(var j=0;j0)e+=1,k==9?f>=i?f-=i:(f=0,e-=1):k>=12352&&k<=12447||k>=12448&&k<=12543||k>=19968&&k<=40959||k>=63744&&k<=64255||k>=13312&&k<=19903?f>=2?f-=2:(f=0,e-=1):f-=1;else break}this.$useWrapMode?(b=h[d][a],e>=b&&(e=b-1)):c&&(e=Math.min(e,c.length));return{row:d,column:e}},this.documentToScreenColumn=function(a,b){return this.documentToScreenPosition(a,b).column},this.$documentToScreenRow=function(a,b){if(!this.$useWrapMode)return[a,0];var c=this.$wrapData,d=0;if(a>c.length-1)return[this.getScreenLength(),c.length==0?0:c[c.length-1].length-1];for(var e=0;e=c[a][f])d++,f++;return[d,f]},this.documentToScreenRow=function(a,b){return this.$documentToScreenRow(a,b)[0]},this.documentToScreenPosition=function(a,b){var c,d=this.getTabSize(),e;b!=null?e=a:(e=a.row,b=a.column);if(!this.$useWrapMode){c=this.getLine(e).substring(0,b),b=this.$getStringScreenWidth(c);return{row:e,column:b}}var f=this.$documentToScreenRow(e,b),g=f[0];if(e>=this.getLength())return{row:g,column:0};var h,i=this.$wrapData[e],j,k=f[1];c=this.getLine(e).substring(i[k-1]||0,b),j=this.$getStringScreenWidth(c);return{row:g,column:j}},this.getScreenLength=function(){if(!this.$useWrapMode)return this.getLength();var a=0;for(var b=0;bb.row||a.row==b.row&&a.column>b.column},this.getRange=function(){var a=this.selectionAnchor,b=this.selectionLead;if(this.isEmpty())return g.fromPoints(b,b);return this.isBackwards()?g.fromPoints(b,a):g.fromPoints(a,b)},this.clearSelection=function(){this.$isEmpty||(this.$isEmpty=!0,this._dispatchEvent("changeSelection"))},this.selectAll=function(){var a=this.doc.getLength()-1;this.setSelectionAnchor(a,this.doc.getLine(a).length),this.moveCursorTo(0,0)},this.setSelectionRange=function(a,b){b?(this.setSelectionAnchor(a.end.row,a.end.column),this.selectTo(a.start.row,a.start.column)):(this.setSelectionAnchor(a.start.row,a.start.column),this.selectTo(a.end.row,a.end.column)),this.$updateDesiredColumn()},this.$updateDesiredColumn=function(){var a=this.getCursor();this.$desiredColumn=this.session.documentToScreenColumn(a.row,a.column)},this.$moveSelection=function(a){var b=this.selectionLead;this.$isEmpty&&this.setSelectionAnchor(b.row,b.column),a.call(this)},this.selectTo=function(a,b){this.$moveSelection(function(){this.moveCursorTo(a,b)})},this.selectToPosition=function(a){this.$moveSelection(function(){this.moveCursorToPosition(a)})},this.selectUp=function(){this.$moveSelection(this.moveCursorUp)},this.selectDown=function(){this.$moveSelection(this.moveCursorDown)},this.selectRight=function(){this.$moveSelection(this.moveCursorRight)},this.selectLeft=function(){this.$moveSelection(this.moveCursorLeft)},this.selectLineStart=function(){this.$moveSelection(this.moveCursorLineStart)},this.selectLineEnd=function(){this.$moveSelection(this.moveCursorLineEnd)},this.selectFileEnd=function(){this.$moveSelection(this.moveCursorFileEnd)},this.selectFileStart=function(){this.$moveSelection(this.moveCursorFileStart)},this.selectWordRight=function(){this.$moveSelection(this.moveCursorWordRight)},this.selectWordLeft=function(){this.$moveSelection(this.moveCursorWordLeft)},this.selectWord=function(){var a=this.getCursor(),b=this.session.getWordRange(a.row,a.column);this.setSelectionRange(b)},this.selectLine=function(){this.setSelectionAnchor(this.selectionLead.row,0),this.$moveSelection(function(){this.moveCursorTo(this.selectionLead.row+1,0)})},this.moveCursorUp=function(){this.moveCursorBy(-1,0)},this.moveCursorDown=function(){this.moveCursorBy(1,0)},this.moveCursorLeft=function(){var a=this.selectionLead.getPosition();if(a.column==0)a.row>0&&this.moveCursorTo(a.row-1,this.doc.getLine(a.row-1).length);else{var b=this.session.getTabSize();this.session.isTabStop(a)&&this.doc.getLine(a.row).slice(a.column-b,a.column).split(" ").length-1==b?this.moveCursorBy(0,-b):this.moveCursorBy(0,-1)}},this.moveCursorRight=function(){if(this.selectionLead.column==this.doc.getLine(this.selectionLead.row).length)this.selectionLead.row=b?this.moveCursorTo(a,d):this.moveCursorTo(a,d+f[0].length)},this.moveCursorLineEnd=function(){var a=this.selectionLead;this.moveCursorTo(a.row,this.session.getDocumentLastRowColumn(a.row,a.column))},this.moveCursorFileEnd=function(){var a=this.doc.getLength()-1,b=this.doc.getLine(a).length;this.moveCursorTo(a,b)},this.moveCursorFileStart=function(){this.moveCursorTo(0,0)},this.moveCursorWordRight=function(){var a=this.selectionLead.row,b=this.selectionLead.column,c=this.doc.getLine(a),d=c.substring(b),e;this.session.nonTokenRe.lastIndex=0,this.session.tokenRe.lastIndex=0;if(b==c.length)this.moveCursorRight();else{if(e=this.session.nonTokenRe.exec(d))b+=this.session.nonTokenRe.lastIndex,this.session.nonTokenRe.lastIndex=0;else if(e=this.session.tokenRe.exec(d))b+=this.session.tokenRe.lastIndex,this.session.tokenRe.lastIndex=0;this.moveCursorTo(a,b)}},this.moveCursorWordLeft=function(){var a=this.selectionLead.row,b=this.selectionLead.column,c=this.doc.getLine(a),d=e.stringReverse(c.substring(0,b)),f;this.session.nonTokenRe.lastIndex=0,this.session.tokenRe.lastIndex=0;if(b==0)this.moveCursorLeft();else{if(f=this.session.nonTokenRe.exec(d))b-=this.session.nonTokenRe.lastIndex,this.session.nonTokenRe.lastIndex=0;else if(f=this.session.tokenRe.exec(d))b-=this.session.tokenRe.lastIndex,this.session.tokenRe.lastIndex=0;this.moveCursorTo(a,b)}},this.moveCursorBy=function(a,b){if(this.session.getUseWrapMode()){var c=this.session.documentToScreenPosition(this.selectionLead.row,this.selectionLead.column),d=b==0&&this.$desiredColumn||c.column,e=this.session.screenToDocumentPosition(c.row+a,d);this.moveCursorTo(e.row,e.column+b,b==0)}else{var f=b==0&&this.$desiredColumn||this.selectionLead.column;this.moveCursorTo(this.selectionLead.row+a,f+b,b==0)}},this.moveCursorToPosition=function(a){this.moveCursorTo(a.row,a.column)},this.moveCursorTo=function(a,b,c){this.selectionLead.setPosition(a,b),c||this.$updateDesiredColumn(this.selectionLead.column)},this.moveCursorToScreen=function(a,b,c){if(this.session.getUseWrapMode()){var d=this.session.screenToDocumentPosition(a,b);a=d.row,b=d.column}this.moveCursorTo(a,b,c)}}).call(h.prototype),b.Selection=h}),define("ace/range",function(a,b,c){var d=function(a,b,c,d){this.start={row:a,column:b},this.end={row:c,column:d}};(function(){this.toString=function(){return"Range: ["+this.start.row+"/"+this.start.column+"] -> ["+this.end.row+"/"+this.end.column+"]"},this.contains=function(a,b){return this.compare(a,b)==0},this.compare=function(a,b){if(!this.isMultiLine()&&a===this.start.row)return bthis.end.column?1:0;if(athis.end.row)return 1;if(this.start.row===a)return b>=this.start.column?0:-1;if(this.end.row===a)return b<=this.end.column?0:1;return 0},this.clipRows=function(a,b){if(this.end.row>b)var c={row:b+1,column:0};if(this.start.row>b)var e={row:b+1,column:0};if(this.start.row=0&&/^[\w\d]/.test(h)||e<=g&&/[\w\d]$/.test(h))return;h=f.substring(c.start.column,c.end.column);if(!/^[\w\d]+$/.test(h))return;var i=a.getCursorPosition(),j={wrap:!0,wholeWord:!0,caseSensitive:!0,needle:h},k=a.$search.getOptions();a.$search.set(j);var l=a.$search.findAll(b);l.forEach(function(a){if(!a.contains(i.row,i.column)){var c=b.addMarker(a,"ace_selected_word");b.$selectionOccurrences.push(c)}}),a.$search.set(k)}},this.clearSelectionHighlight=function(a){!a.session.$selectionOccurrences||(a.session.$selectionOccurrences.forEach(function(b){a.session.removeMarker(b)}),a.session.$selectionOccurrences=[])}}).call(f.prototype),b.Mode=f}),define("ace/tokenizer",function(a,b,c){var d=function(a){this.rules=a,this.regExps={};for(var b in this.rules){var c=this.rules[b],d=c,e=[];for(var f=0;f=b&&(a.row=Math.max(0,b-1),a.column=this.getLine(b-1).length);return a},this.insert=function(a,b){if(b.length==0)return a;a=this.$clipPosition(a),this.getLength()<=1&&this.$detectNewLine(b);var c=this.$split(b),d=c.splice(0,1)[0],e=c.length==0?null:c.splice(c.length-1,1)[0];a=this.insertInLine(a,d),e!==null&&(a=this.insertNewLine(a),a=this.insertLines(a.row,c),a=this.insertInLine(a,e||""));return a},this.insertLines=function(a,b){if(b.length==0)return{row:a,column:0};var c=[a,0];c.push.apply(c,b),this.$lines.splice.apply(this.$lines,c);var d=new f(a,0,a+b.length,0),e={action:"insertLines",range:d,lines:b};this._dispatchEvent("change",{data:e});return d.end},this.insertNewLine=function(a){a=this.$clipPosition(a);var b=this.$lines[a.row]||"";this.$lines[a.row]=b.substring(0,a.column),this.$lines.splice(a.row+1,0,b.substring(a.column,b.length));var c={row:a.row+1,column:0},d={action:"insertText",range:f.fromPoints(a,c),text:this.getNewLineCharacter()};this._dispatchEvent("change",{data:d});return c},this.insertInLine=function(a,b){if(b.length==0)return a;var c=this.$lines[a.row]||"";this.$lines[a.row]=c.substring(0,a.column)+b+c.substring(a.column);var d={row:a.row,column:a.column+b.length},e={action:"insertText",range:f.fromPoints(a,d),text:b};this._dispatchEvent("change",{data:e});return d},this.remove=function(a){a.start=this.$clipPosition(a.start),a.end=this.$clipPosition(a.end);if(a.isEmpty())return a.start;var b=a.start.row,c=a.end.row;if(a.isMultiLine()){var d=a.start.column==0?b:b+1,e=c-1;a.end.column>0&&this.removeInLine(c,0,a.end.column),e>=d&&this.removeLines(d,e),d!=b&&(this.removeInLine(b,a.start.column,this.getLine(b).length),this.removeNewLine(a.start.row))}else this.removeInLine(b,a.start.column,a.end.column);return a.start},this.removeInLine=function(a,b,c){if(b!=c){var d=new f(a,b,a,c),e=this.getLine(a),g=e.substring(b,c),h=e.substring(0,b)+e.substring(c,e.length);this.$lines.splice(a,1,h);var i={action:"removeText",range:d,text:g};this._dispatchEvent("change",{data:i});return d.start}},this.removeLines=function(a,b){var c=new f(a,0,b+1,0),d=this.$lines.splice(a,b-a+1),e={action:"removeLines",range:c,nl:this.getNewLineCharacter(),lines:d};this._dispatchEvent("change",{data:e});return d},this.removeNewLine=function(a){var b=this.getLine(a),c=this.getLine(a+1),d=new f(a,b.length,a+1,0),e=b+c;this.$lines.splice(a,2,e);var g={action:"removeText",range:d,text:this.getNewLineCharacter()};this._dispatchEvent("change",{data:g})},this.replace=function(a,b){if(b.length==0&&a.isEmpty())return a.start;if(b==this.getTextRange(a))return a.end;this.remove(a);if(b)var c=this.insert(a.start,b);else c=a.start;return c},this.applyDeltas=function(a){for(var b=0;b=0;b--){var c=a[b],d=f.fromPoints(c.range.start,c.range.end);c.action=="insertLines"?this.removeLines(d.start.row,d.end.row-1):c.action=="insertText"?this.remove(d):c.action=="removeLines"?this.insertLines(d.start.row,c.lines):c.action=="removeText"&&this.insert(d.start,c.text)}}}).call(h.prototype),b.Document=h}),define("ace/anchor",function(a,b,c){var d=a("pilot/oop"),e=a("pilot/event_emitter").EventEmitter,f=b.Anchor=function(a,b,c){this.document=a,typeof c=="undefined"?this.setPosition(b.row,b.column):this.setPosition(b,c),this.$onChange=this.onChange.bind(this),a.on("change",this.$onChange)};(function(){d.implement(this,e),this.getPosition=function(){return this.$clipPositionToDocument(this.row,this.column)},this.getDocument=function(){return this.document},this.onChange=function(a){var b=a.data,c=b.range;if(c.start.row!=c.end.row||c.start.row==this.row){if(c.start.row>this.row)return;if(c.start.row==this.row&&c.start.column>this.column)return;var d=this.row,e=this.column;b.action==="insertText"?c.start.row===d&&c.start.column<=e?c.start.row===c.end.row?e+=c.end.column-c.start.column:(e-=c.start.column,d+=c.end.row-c.start.row):c.start.row!==c.end.row&&c.start.row=e?e=c.start.column:e=Math.max(0,e-(c.end.column-c.start.column)):c.start.row!==c.end.row&&c.start.row=this.document.getLength()?(c.row=Math.max(0,this.document.getLength()-1),c.column=this.document.getLine(c.row).length):a<0?(c.row=0,c.column=0):(c.row=a,c.column=Math.min(this.document.getLine(c.row).length,Math.max(0,b))),b<0&&(c.column=0);return c}}).call(f.prototype)}),define("ace/search",function(a,b,c){var d=a("pilot/lang"),e=a("pilot/oop"),f=a("ace/range").Range,g=function(){this.$options={needle:"",backwards:!1,wrap:!1,caseSensitive:!1,wholeWord:!1,scope:g.ALL,regExp:!1}};g.ALL=1,g.SELECTION=2,function(){this.set=function(a){e.mixin(this.$options,a);return this},this.getOptions=function(){return d.copyObject(this.$options)},this.find=function(a){if(!this.$options.needle)return null;if(this.$options.backwards)var b=this.$backwardMatchIterator(a);else b=this.$forwardMatchIterator(a);var c=null;b.forEach(function(a){c=a;return!0});return c},this.findAll=function(a){if(!this.$options.needle)return[];if(this.$options.backwards)var b=this.$backwardMatchIterator(a);else b=this.$forwardMatchIterator(a);var c=[];b.forEach(function(a){c.push(a)});return c},this.replace=function(a,b){var c=this.$assembleRegExp(),d=c.exec(a);return d&&d[0].length==a.length?this.$options.regExp?a.replace(c,b):b:null},this.$forwardMatchIterator=function(a){var b=this.$assembleRegExp(),c=this;return{forEach:function(d){c.$forwardLineIterator(a).forEach(function(a,e,f){e&&(a=a.substring(e));var g=[];a.replace(b,function(a){var b=arguments[arguments.length-2];g.push({str:a,offset:e+b});return a});for(var h=0;h=0;h--){var i=g[h],j=c.$rangeFromMatch(f,i.offset,i.str.length);if(d(j))return!0}})}}},this.$rangeFromMatch=function(a,b,c){return new f(a,b,a,b+c)},this.$assembleRegExp=function(){if(this.$options.regExp)var a=this.$options.needle;else a=d.escapeRegExp(this.$options.needle);this.$options.wholeWord&&(a="\\b"+a+"\\b");var b="g";this.$options.caseSensitive||(b+="i");var c=new RegExp(a,b);return c},this.$forwardLineIterator=function(a){function k(e){var f=a.getLine(e);b&&e==c.end.row&&(f=f.substring(0,c.end.column)),j&&e==d.row&&(f=f.substring(0,d.column));return f}var b=this.$options.scope==g.SELECTION,c=a.getSelection().getRange(),d=a.getSelection().getCursor(),e=b?c.start.row:0,f=b?c.start.column:0,h=b?c.end.row:a.getLength()-1,i=this.$options.wrap,j=!1;return{forEach:function(a){var b=d.row,c=k(b),g=d.column,l=!1;j=!1;while(!a(c,g,b)){if(l)return;b++,g=0;if(b>h)if(i)b=e,g=f,j=!0;else return;b==d.row&&(l=!0),c=k(b)}}}},this.$backwardLineIterator=function(a){var b=this.$options.scope==g.SELECTION,c=a.getSelection().getRange(),d=b?c.end:c.start,e=b?c.start.row:0,f=b?c.start.column:0,h=b?c.end.row:a.getLength()-1,i=this.$options.wrap;return{forEach:function(g){var j=d.row,k=a.getLine(j).substring(0,d.column),l=0,m=!1,n=!1;while(!g(k,l,j)){if(m)return;j--,l=0;if(j20){c.fireUpdateEvent(d,c.currentLine-1);var i=c.currentLine0&&this.lines[a-1]&&(d=this.lines[a-1].state,e=!0);var f=this.doc.getLines(a,b);for(var g=a;g<=b;g++)if(!this.lines[g]){var h=this.tokenizer.getLineTokens(f[g-a]||"",d),d=h.state;c.push(h),e&&(this.lines[g]=h)}else{var h=this.lines[g];d=h.state,c.push(h)}return c}}).call(f.prototype),b.BackgroundTokenizer=f}),define("ace/undomanager",function(a,b,c){var d=function(){this.reset()};(function(){this.execute=function(a){var b=a.args[0];this.$doc=a.args[1],this.$undoStack.push(b)},this.undo=function(){var a=this.$undoStack.pop();a&&(this.$doc.undoChanges(a),this.$redoStack.push(a))},this.redo=function(){var a=this.$redoStack.pop();a&&(this.$doc.redoChanges(a),this.$undoStack.push(a))},this.reset=function(){this.$undoStack=[],this.$redoStack=[]},this.hasUndo=function(){return this.$undoStack.length>0},this.hasRedo=function(){return this.$redoStack.length>0}}).call(d.prototype),b.UndoManager=d}),define("ace/theme/textmate",function(a,b,c){var d=a("pilot/dom"),e=".ace-tm .ace_editor {\n border: 2px solid rgb(159, 159, 159);\n}\n\n.ace-tm .ace_editor.ace_focus {\n border: 2px solid #327fbd;\n}\n\n.ace-tm .ace_gutter {\n width: 50px;\n background: #e8e8e8;\n color: #333;\n overflow : hidden;\n}\n\n.ace-tm .ace_gutter-layer {\n width: 100%;\n text-align: right;\n}\n\n.ace-tm .ace_gutter-layer .ace_gutter-cell {\n padding-right: 6px;\n}\n\n.ace-tm .ace_print_margin {\n width: 1px;\n background: #e8e8e8;\n}\n\n.ace-tm .ace_text-layer {\n cursor: text;\n}\n\n.ace-tm .ace_cursor {\n border-left: 2px solid black;\n}\n\n.ace-tm .ace_cursor.ace_overwrite {\n border-left: 0px;\n border-bottom: 1px solid black;\n}\n \n.ace-tm .ace_line .ace_invisible {\n color: rgb(191, 191, 191);\n}\n\n.ace-tm .ace_line .ace_keyword {\n color: blue;\n}\n\n.ace-tm .ace_line .ace_constant.ace_buildin {\n color: rgb(88, 72, 246);\n}\n\n.ace-tm .ace_line .ace_constant.ace_language {\n color: rgb(88, 92, 246);\n}\n\n.ace-tm .ace_line .ace_constant.ace_library {\n color: rgb(6, 150, 14);\n}\n\n.ace-tm .ace_line .ace_invalid {\n background-color: rgb(153, 0, 0);\n color: white;\n}\n\n.ace-tm .ace_line .ace_support.ace_function {\n color: rgb(60, 76, 114);\n}\n\n.ace-tm .ace_line .ace_support.ace_constant {\n color: rgb(6, 150, 14);\n}\n\n.ace-tm .ace_line .ace_support.ace_type,\n.ace-tm .ace_line .ace_support.ace_class {\n color: rgb(109, 121, 222);\n}\n\n.ace-tm .ace_line .ace_keyword.ace_operator {\n color: rgb(104, 118, 135);\n}\n\n.ace-tm .ace_line .ace_string {\n color: rgb(3, 106, 7);\n}\n\n.ace-tm .ace_line .ace_comment {\n color: rgb(76, 136, 107);\n}\n\n.ace-tm .ace_line .ace_comment.ace_doc {\n color: rgb(0, 102, 255);\n}\n\n.ace-tm .ace_line .ace_comment.ace_doc.ace_tag {\n color: rgb(128, 159, 191);\n}\n\n.ace-tm .ace_line .ace_constant.ace_numeric {\n color: rgb(0, 0, 205);\n}\n\n.ace-tm .ace_line .ace_variable {\n color: rgb(49, 132, 149);\n}\n\n.ace-tm .ace_line .ace_xml_pe {\n color: rgb(104, 104, 91);\n}\n\n.ace-tm .ace_marker-layer .ace_selection {\n background: rgb(181, 213, 255);\n}\n\n.ace-tm .ace_marker-layer .ace_step {\n background: rgb(252, 255, 0);\n}\n\n.ace-tm .ace_marker-layer .ace_stack {\n background: rgb(164, 229, 101);\n}\n\n.ace-tm .ace_marker-layer .ace_bracket {\n margin: -1px 0 0 -1px;\n border: 1px solid rgb(192, 192, 192);\n}\n\n.ace-tm .ace_marker-layer .ace_active_line {\n background: rgb(232, 242, 254);\n}\n\n.ace-tm .ace_marker-layer .ace_selected_word {\n background: rgb(250, 250, 255);\n border: 1px solid rgb(200, 200, 250);\n}\n\n.ace-tm .ace_string.ace_regex {\n color: rgb(255, 0, 0)\n}";d.importCssString(e),b.cssClass="ace-tm"}),define("ace/mode/matching_brace_outdent",function(a,b,c){var d=a("ace/range").Range,e=function(){};(function(){this.checkOutdent=function(a,b){if(!/^\s+$/.test(a))return!1;return/^\s*\}/.test(b)},this.autoOutdent=function(a,b){var c=a.getLine(b),e=c.match(/^(\s*\})/);if(!e)return 0;var f=e[1].length,g=a.findMatchingBracket({row:b,column:f});if(!g||g.row==b)return 0;var h=this.$getIndent(a.getLine(g.row));a.replace(new d(b,0,b,f-1),h)},this.$getIndent=function(a){var b=a.match(/^(\s+)/);if(b)return b[1];return""}}).call(e.prototype),b.MatchingBraceOutdent=e}),define("ace/virtual_renderer",function(a,b,c){var d=a("pilot/oop"),e=a("pilot/dom"),f=a("pilot/event"),g=a("pilot/useragent"),h=a("ace/layer/gutter").Gutter,i=a("ace/layer/marker").Marker,j=a("ace/layer/text").Text,k=a("ace/layer/cursor").Cursor,l=a("ace/scrollbar").ScrollBar,m=a("ace/renderloop").RenderLoop,n=a("pilot/event_emitter").EventEmitter,o=a("text/ace/css/editor.css");e.importCssString(o);var p=function(a,b){this.container=a,e.addCssClass(this.container,"ace_editor"),this.setTheme(b),this.$gutter=e.createElement("div"),this.$gutter.className="ace_gutter",this.container.appendChild(this.$gutter),this.scroller=e.createElement("div"),this.scroller.className="ace_scroller",this.container.appendChild(this.scroller),this.content=e.createElement("div"),this.content.className="ace_content",this.scroller.appendChild(this.content),this.$gutterLayer=new h(this.$gutter),this.$markerBack=new i(this.content);var c=this.$textLayer=new j(this.content);this.canvas=c.element,this.$markerFront=new i(this.content),this.characterWidth=c.getCharacterWidth(),this.lineHeight=c.getLineHeight(),this.$cursorLayer=new k(this.content),this.$cursorPadding=8,this.$horizScroll=!0,this.$horizScrollAlwaysVisible=!0,this.scrollBar=new l(a),this.scrollBar.addEventListener("scroll",this.onScroll.bind(this)),this.scrollTop=0,this.cursorPos={row:0,column:0};var d=this;this.$textLayer.addEventListener("changeCharaterSize",function(){d.characterWidth=c.getCharacterWidth(),d.lineHeight=c.getLineHeight(),d.$updatePrintMargin(),d.onResize(!0),d.$loop.schedule(d.CHANGE_FULL)}),f.addListener(this.$gutter,"click",this.$onGutterClick.bind(this)),f.addListener(this.$gutter,"dblclick",this.$onGutterClick.bind(this)),this.$size={width:0,height:0,scrollerHeight:0,scrollerWidth:0},this.$loop=new m(this.$renderChanges.bind(this)),this.$loop.schedule(this.CHANGE_FULL),this.setPadding(4),this.$updatePrintMargin()};(function(){this.showGutter=!0,this.CHANGE_CURSOR=1,this.CHANGE_MARKER=2,this.CHANGE_GUTTER=4,this.CHANGE_SCROLL=8,this.CHANGE_LINES=16,this.CHANGE_TEXT=32,this.CHANGE_SIZE=64,this.CHANGE_MARKER_BACK=128,this.CHANGE_MARKER_FRONT=256,this.CHANGE_FULL=512,d.implement(this,n),this.setSession=function(a){this.session=a,this.$cursorLayer.setSession(a),this.$markerBack.setSession(a),this.$markerFront.setSession(a),this.$gutterLayer.setSession(a),this.$textLayer.setSession(a),this.$loop.schedule(this.CHANGE_FULL)},this.updateLines=function(a,b){b===undefined&&(b=Infinity),this.$changedLines?(this.$changedLines.firstRow>a&&(this.$changedLines.firstRow=a),this.$changedLines.lastRowc.lastRow+1)){if(bc&&this.scrollToY(c),this.getScrollTop()+this.$size.scrollerHeightb&&this.scrollToX(b),this.scroller.scrollLeft+this.$size.scrollerWidththis.scroller.scrollWidth&&this.$renderChanges(this.CHANGE_SIZE),this.scrollToX(Math.round(b+this.characterWidth-this.$size.scrollerWidth)))}},this.getScrollTop=function(){return this.scrollTop},this.getScrollLeft=function(){return this.scroller.scrollLeft},this.getScrollTopRow=function(){return this.scrollTop/this.lineHeight},this.getScrollBottomRow=function(){return Math.max(0,Math.floor((this.scrollTop+this.$size.scrollerHeight)/this.lineHeight)-1)},this.scrollToRow=function(a){this.scrollToY(a*this.lineHeight)},this.scrollToLine=function(a,b){var c={lineHeight:this.lineHeight},d=0;for(var e=1;e",c+1,"")}this.element=d.setInnerHtml(this.element,b.join("")),this.element.style.height=a.minHeight+"px"}}).call(e.prototype),b.Gutter=e}),define("ace/layer/marker",function(a,b,c){var d=a("ace/range").Range,e=a("pilot/dom"),f=function(a){this.element=e.createElement("div"),this.element.className="ace_layer ace_marker-layer",a.appendChild(this.element)};(function(){this.setSession=function(a){this.session=a},this.setMarkers=function(a){this.markers=a},this.update=function(a){var a=a||this.config;if(!!a){this.config=a;var b=[];for(var c in this.markers){var d=this.markers[c],f=d.range.clipRows(a.firstRow,a.lastRow);if(f.isEmpty())continue;f=f.toScreenRange(this.session);if(d.renderer){var g=this.$getTop(f.start.row,a),h=Math.round(f.start.column*a.characterWidth);d.renderer(b,f,h,g,a)}else f.isMultiLine()?d.type=="text"?this.drawTextMarker(b,f,d.clazz,a):this.drawMultiLineMarker(b,f,d.clazz,a):this.drawSingleLineMarker(b,f,d.clazz,a)}this.element=e.setInnerHtml(this.element,b.join(""))}},this.$getTop=function(a,b){return(a-b.firstRowScreen)*b.lineHeight},this.drawTextMarker=function(a,b,c,e){var f=b.start.row,g=new d(f,b.start.column,f,this.session.getScreenLastRowColumn(f));this.drawSingleLineMarker(a,g,c,e,1);var f=b.end.row,g=new d(f,0,f,b.end.column);this.drawSingleLineMarker(a,g,c,e);for(var f=b.start.row+1;f");var g=this.$getTop(b.end.row,d),f=Math.round(b.end.column*d.characterWidth);a.push("
");var e=(b.end.row-b.start.row-1)*d.lineHeight;if(!(e<0)){var g=this.$getTop(b.start.row+1,d);a.push("
")}},this.drawSingleLineMarker=function(a,b,c,d,e){var f=d.lineHeight,g=Math.round((b.end.column+(e||0)-b.start.column)*d.characterWidth),h=this.$getTop(b.start.row,d),i=Math.round(b.start.column*d.characterWidth);a.push("
")}}).call(f.prototype),b.Marker=f}),define("ace/layer/text",function(a,b,c){var d=a("pilot/oop"),e=a("pilot/dom"),f=a("pilot/lang"),g=a("pilot/event_emitter").EventEmitter,h=function(a){this.element=e.createElement("div"),this.element.className="ace_layer ace_text-layer",a.appendChild(this.element),this.$characterSize=this.$measureSizes()||{width:0,height:0},this.$pollSizeChanges()};(function(){d.implement(this,g),this.EOF_CHAR="¶",this.EOL_CHAR="¬",this.TAB_CHAR="→",this.SPACE_CHAR="·",this.setTokenizer=function(a){this.tokenizer=a},this.getLineHeight=function(){return this.$characterSize.height||1},this.getCharacterWidth=function(){return this.$characterSize.width||1},this.checkForSizeChanges=function(){var a=this.$measureSizes();a&&(this.$characterSize.width!==a.width||this.$characterSize.height!==a.height)&&(this.$characterSize=a,this._dispatchEvent("changeCharaterSize",{data:a}))},this.$pollSizeChanges=function(){var a=this;setInterval(function(){a.checkForSizeChanges()},500)},this.$fontStyles={fontFamily:1,fontSize:1,fontWeight:1,fontStyle:1,lineHeight:1},this.$measureSizes=function(){var a=1e3;if(!this.$measureNode){var b=this.$measureNode=e.createElement("div"),c=b.style;c.width=c.height="auto",c.left=c.top=-a*40+"px",c.visibility="hidden",c.position="absolute",c.overflow="visible",c.whiteSpace="nowrap",b.innerHTML=f.stringRepeat("Xy",a);if(document.body)document.body.appendChild(b);else{var d=this.element.parentNode;while(!e.hasCssClass(d,"ace_editor"))d=d.parentNode;d.appendChild(b)}}var c=this.$measureNode.style;for(var g in this.$fontStyles){var h=e.computedStyle(this.element,g);c[g]=h}var i={height:this.$measureNode.offsetHeight,width:this.$measureNode.offsetWidth/(a*2)};if(i.width==0&&i.height==0)return null;return i},this.setSession=function(a){this.session=a},this.showInvisibles=!1,this.setShowInvisibles=function(a){if(this.showInvisibles==a)return!1;this.showInvisibles=a;return!0},this.$computeTabString=function(){var a=this.session.getTabSize();if(this.showInvisibles){var b=a/2;this.$tabString=""+Array(Math.floor(b)).join(" ")+this.TAB_CHAR+Array(Math.ceil(b)+1).join(" ")+""}else this.$tabString=Array(a+1).join(" ")},this.updateLines=function(a,b,c){this.$computeTabString(),(this.config.lastRow!=a.lastRow||this.config.firstRow!=a.firstRow)&&this.scrollLines(a),this.config=a;var d=Math.max(b,a.firstRow),f=Math.min(c,a.lastRow),g=this.element.childNodes,h=this.tokenizer.getTokens(d,f);for(var i=d;i<=f;i++){var j=g[i-a.firstRow];if(!j)continue;var k=[];this.$renderLine(k,i,h[i-d].tokens),j=e.setInnerHtml(j,k.join("")),j.style.height=this.session.getRowHeight(a,i)+"px"}},this.scrollLines=function(a){this.$computeTabString();var b=this.config;this.config=a;if(!b||b.lastRowa.lastRow)for(var d=a.lastRow+1;d<=b.lastRow;d++)c.removeChild(c.lastChild);if(a.firstRowb.lastRow){var e=this.$renderLinesFragment(a,b.lastRow+1,a.lastRow);c.appendChild(e)}},this.$renderLinesFragment=function(a,b,c){var d=document.createDocumentFragment(),f=this.tokenizer.getTokens(b,c);for(var g=b;g<=c;g++){var h=e.createElement("div");h.className="ace_line";var i=h.style;i.height=this.session.getRowHeight(a,g)+"px",i.width=a.width+"px";var j=[];f.length>g-b&&this.$renderLine(j,g,f[g-b].tokens),h.innerHTML=j.join(""),d.appendChild(h)}return d},this.update=function(a){this.$computeTabString(),this.config=a;var b=[],c=this.tokenizer.getTokens(a.firstRow,a.lastRow),d=this.$renderLinesFragment(a,a.firstRow,a.lastRow);this.element.innerHTML="",this.element.appendChild(d)},this.$textToken={text:!0,rparen:!0,lparen:!0},this.$renderLine=function(a,b,c){function i(b,c){var d=c.replace(/&/g,"&").replace(/"+a+""});if(!g.$textToken[b.type]){var i="ace_"+b.type.replace(/\./g," ace_");a.push("",d,"")}else a.push(d)}if(this.showInvisibles)var d=this,e=/( +)|([\v\f \u00a0\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u3000])/g,f=function(a){if(a.charCodeAt(0)==32)return Array(a.length+1).join(" ");var a=Array(a.length+1).join(d.SPACE_CHAR);return""+a+""};else var e=/[\v\f \u00a0\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u3000]/g,f=" ";var g=this,h=this.config.characterWidth,j=this.session.getRowSplitData(b),k=0,l=0,m;!j||j.length==0?m=Number.MAX_VALUE:m=j[0],a.push("
");for(var n=0;n=m)i(o,p.substring(0,m-k)),p=p.substring(m-k),k=m,a.push("
","
"),l++,m=j[l]||Number.MAX_VALUE;p.length!=0&&(k+=p.length,i(o,p))}}this.showInvisibles&&(b!==this.session.getLength()-1?a.push(""+this.EOL_CHAR+""):a.push(""+this.EOF_CHAR+"")),a.push("
")}}).call(h.prototype),b.Text=h}),define("ace/layer/cursor",function(a,b,c){var d=a("pilot/dom"),e=function(a){this.element=d.createElement("div"),this.element.className="ace_layer ace_cursor-layer",a.appendChild(this.element),this.cursor=d.createElement("div"),this.cursor.className="ace_cursor",this.isVisible=!1};(function(){this.setSession=function(a){this.session=a},this.hideCursor=function(){this.isVisible=!1,this.cursor.parentNode&&this.cursor.parentNode.removeChild(this.cursor),clearInterval(this.blinkId)},this.showCursor=function(){this.isVisible=!0,this.element.appendChild(this.cursor);var a=this.cursor;a.style.visibility="visible",this.restartTimer()},this.restartTimer=function(){clearInterval(this.blinkId);if(!!this.isVisible){var a=this.cursor;this.blinkId=setInterval(function(){a.style.visibility="hidden",setTimeout(function(){a.style.visibility="visible"},400)},1e3)}},this.getPixelPosition=function(a){if(!this.config||!this.session)return{left:0,top:0};var b=this.session.selection.getCursor(),c=this.session.documentToScreenPosition(b),d=Math.round(c.column*this.config.characterWidth),e=(c.row-(a?this.config.firstRowScreen:0))*this.config.lineHeight;return{left:d,top:e}},this.update=function(a){this.config=a,this.pixelPos=this.getPixelPosition(!0),this.cursor.style.left=this.pixelPos.left+"px",this.cursor.style.top=this.pixelPos.top+"px",this.cursor.style.width=a.characterWidth+"px",this.cursor.style.height=a.lineHeight+"px",this.isVisible&&this.element.appendChild(this.cursor),this.session.getOverwrite()?d.addCssClass(this.cursor,"ace_overwrite"):d.removeCssClass(this.cursor,"ace_overwrite"),this.restartTimer()}}).call(e.prototype),b.Cursor=e}),define("ace/scrollbar",function(a,b,c){var d=a("pilot/oop"),e=a("pilot/dom"),f=a("pilot/event"),g=a("pilot/event_emitter").EventEmitter,h=function(a){this.element=e.createElement("div"),this.element.className="ace_sb",this.inner=e.createElement("div"),this.element.appendChild(this.inner),a.appendChild(this.element),this.width=e.scrollbarWidth(),this.element.style.width=this.width+"px",f.addListener(this.element,"scroll",this.onScroll.bind(this))};(function(){d.implement(this,g),this.onScroll=function(){this._dispatchEvent("scroll",{data:this.element.scrollTop})},this.getWidth=function(){return this.width},this.setHeight=function(a){this.element.style.height=a+"px"},this.setInnerHeight=function(a){this.inner.style.height=a+"px"},this.setScrollTop=function(a){this.element.scrollTop=a}}).call(h.prototype),b.ScrollBar=h}),define("ace/renderloop",function(a,b,c){var d=a("pilot/event"),e=function(a){this.onRender=a,this.pending=!1,this.changes=0};(function(){this.schedule=function(a){this.changes=this.changes|a;if(!this.pending){this.pending=!0;var b=this;this.setTimeoutZero(function(){b.pending=!1;var a=b.changes;b.changes=0,b.onRender(a)})}},window.postMessage?(this.messageName="zero-timeout-message",this.setTimeoutZero=function(a){if(!this.attached){var b=this;d.addListener(window,"message",function(a){b.callback&&a.data==b.messageName&&(d.stopPropagation(a),b.callback())}),this.attached=!0}this.callback=a,window.postMessage(this.messageName,"*")}):this.setTimeoutZero=function(a){setTimeout(a,0)}}).call(e.prototype),b.RenderLoop=e}),define("text/ace/css/editor.css",'.ace_editor { position: absolute; overflow: hidden; font-family: "Menlo", "Monaco", "Courier New", monospace; font-size: 12px; }.ace_scroller { position: absolute; overflow-x: scroll; overflow-y: hidden; }.ace_content { position: absolute; box-sizing: border-box; -moz-box-sizing: border-box; -webkit-box-sizing: border-box;}.ace_composition { position: absolute; background: #555; color: #DDD; z-index: 4;}.ace_gutter { position: absolute; overflow-x: hidden; overflow-y: hidden; height: 100%;}.ace_gutter-cell.ace_error { background-image: url("data:image/gif,GIF89a%10%00%10%00%D5%00%00%F5or%F5%87%88%F5nr%F4ns%EBmq%F5z%7F%DDJT%DEKS%DFOW%F1Yc%F2ah%CE(7%CE)8%D18E%DD%40M%F2KZ%EBU%60%F4%60m%DCir%C8%16(%C8%19*%CE%255%F1%3FR%F1%3FS%E6%AB%B5%CA%5DI%CEn%5E%F7%A2%9A%C9G%3E%E0a%5B%F7%89%85%F5yy%F6%82%80%ED%82%80%FF%BF%BF%E3%C4%C4%FF%FF%FF%FF%FF%FF%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00!%F9%04%01%00%00%25%00%2C%00%00%00%00%10%00%10%00%00%06p%C0%92pH%2C%1A%8F%C8%D2H%93%E1d4%23%E4%88%D3%09mB%1DN%B48%F5%90%40%60%92G%5B%94%20%3E%22%D2%87%24%FA%20%24%C5%06A%00%20%B1%07%02B%A38%89X.v%17%82%11%13q%10%0Fi%24%0F%8B%10%7BD%12%0Ei%09%92%09%0EpD%18%15%24%0A%9Ci%05%0C%18F%18%0B%07%04%01%04%06%A0H%18%12%0D%14%0D%12%A1I%B3%B4%B5IA%00%3B"); background-repeat: no-repeat; background-position: 4px center;}.ace_gutter-cell.ace_warning { background-image: url("data:image/gif,GIF89a%10%00%10%00%D5%00%00%FF%DBr%FF%DE%81%FF%E2%8D%FF%E2%8F%FF%E4%96%FF%E3%97%FF%E5%9D%FF%E6%9E%FF%EE%C1%FF%C8Z%FF%CDk%FF%D0s%FF%D4%81%FF%D5%82%FF%D5%83%FF%DC%97%FF%DE%9D%FF%E7%B8%FF%CCl%7BQ%13%80U%15%82W%16%81U%16%89%5B%18%87%5B%18%8C%5E%1A%94d%1D%C5%83-%C9%87%2F%C6%84.%C6%85.%CD%8B2%C9%871%CB%8A3%CD%8B5%DC%98%3F%DF%9BB%E0%9CC%E1%A5U%CB%871%CF%8B5%D1%8D6%DB%97%40%DF%9AB%DD%99B%E3%B0p%E7%CC%AE%FF%FF%FF%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00!%F9%04%01%00%00%2F%00%2C%00%00%00%00%10%00%10%00%00%06a%C0%97pH%2C%1A%8FH%A1%ABTr%25%87%2B%04%82%F4%7C%B9X%91%08%CB%99%1C!%26%13%84*iJ9(%15G%CA%84%14%01%1A%97%0C%03%80%3A%9A%3E%81%84%3E%11%08%B1%8B%20%02%12%0F%18%1A%0F%0A%03\'F%1C%04%0B%10%16%18%10%0B%05%1CF%1D-%06%07%9A%9A-%1EG%1B%A0%A1%A0U%A4%A5%A6BA%00%3B"); background-repeat: no-repeat; background-position: 4px center;}.ace_editor .ace_sb { position: absolute; overflow-x: hidden; overflow-y: scroll; right: 0;}.ace_editor .ace_sb div { position: absolute; width: 1px; left: 0;}.ace_editor .ace_print_margin_layer { z-index: 0; position: absolute; overflow: hidden; margin: 0; left: 0; height: 100%; width: 100%;}.ace_editor .ace_print_margin { position: absolute; height: 100%;}.ace_editor textarea { position: fixed; z-index: -1; width: 10px; height: 30px; opacity: 0; background: transparent; appearance: none; border: none; resize: none; outline: none; overflow: hidden;}.ace_layer { z-index: 1; position: absolute; overflow: hidden; white-space: nowrap; height: 100%; width: 100%;}.ace_text-layer { font-family: Monaco, "Courier New", monospace; color: black;}.ace_cjk { display: inline-block; text-align: center;}.ace_cursor-layer { z-index: 4; cursor: text; pointer-events: none;}.ace_cursor { z-index: 4; position: absolute;}.ace_line { white-space: nowrap;}.ace_marker-layer { cursor: text;}.ace_marker-layer .ace_step { position: absolute; z-index: 3;}.ace_marker-layer .ace_selection { position: absolute; z-index: 4;}.ace_marker-layer .ace_bracket { position: absolute; z-index: 5;}.ace_marker-layer .ace_active_line { position: absolute; z-index: 2;}.ace_marker-layer .ace_selected_word { position: absolute; z-index: 6; box-sizing: border-box; -moz-box-sizing: border-box; -webkit-box-sizing: border-box;}.ace_dragging .ace_marker-layer, .ace_dragging .ace_text-layer { cursor: move;}'),define("text/styles.css","html { height: 100%; overflow: hidden;}body { overflow: hidden; margin: 0; padding: 0; height: 100%; width: 100%; font-family: Arial, Helvetica, sans-serif, Tahoma, Verdana, sans-serif; font-size: 12px; background: rgb(14, 98, 165); color: white;}#editor { position: absolute; top: 60px; left: 0px; background: white;}#controls { width: 100%;}#cockpitInput { position: absolute; width: 100%; bottom: 0; border: none; outline: none; font-family: consolas, courier, monospace; font-size: 120%;}#cockpitOutput { padding: 10px; margin: 0 15px; border: 1px solid #AAA; -moz-border-radius-topleft: 10px; -moz-border-radius-topright: 10px; border-top-left-radius: 4px; border-top-right-radius: 4px; background: #DDD; color: #000;}");var deps=["pilot/fixoldbrowsers","pilot/index","pilot/plugin_manager","pilot/environment","ace/editor","ace/edit_session","ace/virtual_renderer","ace/undomanager","ace/theme/textmate"];require(deps,function(){var a=require("pilot/plugin_manager").catalog;a.registerPlugins(["pilot/index"]);var b=require("pilot/dom"),c=require("pilot/event"),d=require("ace/editor").Editor,e=require("ace/edit_session").EditSession,f=require("ace/undomanager").UndoManager,g=require("ace/virtual_renderer").VirtualRenderer;window.ace={edit:function(h){typeof h=="string"&&(h=document.getElementById(h));var i=new e(b.getInnerText(h));i.setUndoManager(new f),h.innerHTML="";var j=new d(new g(h,"ace/theme/textmate"));j.setSession(i);var k=require("pilot/environment").create();a.startupPlugins({env:k}).then(function(){k.document=i,k.editor=j,j.resize(),c.addListener(window,"resize",function(){j.resize()}),h.env=k}),j.env=k;return j}}}) ================================================ FILE: dirigible/shared/static/ace/cockpit-uncompressed.js ================================================ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Mozilla Skywriter. * * The Initial Developer of the Original Code is * Mozilla. * Portions created by the Initial Developer are Copyright (C) 2009 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Kevin Dangoor (kdangoor@mozilla.com) * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('cockpit/index', function(require, exports, module) { exports.startup = function(data, reason) { require('pilot/index'); require('cockpit/cli').startup(data, reason); // window.testCli = require('cockpit/test/testCli'); require('cockpit/ui/settings').startup(data, reason); require('cockpit/ui/cli_view').startup(data, reason); require('cockpit/commands/basic').startup(data, reason); }; /* exports.shutdown(data, reason) { }; */ }); /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Skywriter. * * The Initial Developer of the Original Code is * Mozilla. * Portions created by the Initial Developer are Copyright (C) 2009 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Joe Walker (jwalker@mozilla.com) * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('cockpit/cli', function(require, exports, module) { var console = require('pilot/console'); var lang = require('pilot/lang'); var oop = require('pilot/oop'); var EventEmitter = require('pilot/event_emitter').EventEmitter; //var keyboard = require('keyboard/keyboard'); var types = require('pilot/types'); var Status = require('pilot/types').Status; var Conversion = require('pilot/types').Conversion; var canon = require('pilot/canon'); /** * Normally type upgrade is done when the owning command is registered, but * out commandParam isn't part of a command, so it misses out. */ exports.startup = function(data, reason) { canon.upgradeType('command', commandParam); }; /** * The information required to tell the user there is a problem with their * input. * TODO: There a several places where {start,end} crop up. Perhaps we should * have a Cursor object. */ function Hint(status, message, start, end, predictions) { this.status = status; this.message = message; if (typeof start === 'number') { this.start = start; this.end = end; this.predictions = predictions; } else { var arg = start; this.start = arg.start; this.end = arg.end; this.predictions = arg.predictions; } } Hint.prototype = { }; /** * Loop over the array of hints finding the one we should display. * @param hints array of hints */ Hint.sort = function(hints, cursor) { // Calculate 'distance from cursor' if (cursor !== undefined) { hints.forEach(function(hint) { if (hint.start === Argument.AT_CURSOR) { hint.distance = 0; } else if (cursor < hint.start) { hint.distance = hint.start - cursor; } else if (cursor > hint.end) { hint.distance = cursor - hint.end; } else { hint.distance = 0; } }, this); } // Sort hints.sort(function(hint1, hint2) { // Compare first based on distance from cursor if (cursor !== undefined) { var diff = hint1.distance - hint2.distance; if (diff != 0) { return diff; } } // otherwise go with hint severity return hint2.status - hint1.status; }); // tidy-up if (cursor !== undefined) { hints.forEach(function(hint) { delete hint.distance; }, this); } return hints; }; exports.Hint = Hint; /** * A Hint that arose as a result of a Conversion */ function ConversionHint(conversion, arg) { this.status = conversion.status; this.message = conversion.message; if (arg) { this.start = arg.start; this.end = arg.end; } else { this.start = 0; this.end = 0; } this.predictions = conversion.predictions; }; oop.inherits(ConversionHint, Hint); /** * We record where in the input string an argument comes so we can report errors * against those string positions. * We publish a 'change' event when-ever the text changes * @param emitter Arguments use something else to pass on change events. * Currently this will be the creating Requisition. This prevents dependency * loops and prevents us from needing to merge listener lists. * @param text The string (trimmed) that contains the argument * @param start The position of the text in the original input string * @param end See start * @param prefix Knowledge of quotation marks and whitespace used prior to the * text in the input string allows us to re-generate the original input from * the arguments. * @param suffix Any quotation marks and whitespace used after the text. * Whitespace is normally placed in the prefix to the succeeding argument, but * can be used here when this is the last argument. * @constructor */ function Argument(emitter, text, start, end, prefix, suffix) { this.emitter = emitter; this.setText(text); this.start = start; this.end = end; this.prefix = prefix; this.suffix = suffix; } Argument.prototype = { /** * Return the result of merging these arguments. * TODO: What happens when we're merging arguments for the single string * case and some of the arguments are in quotation marks? */ merge: function(following) { if (following.emitter != this.emitter) { throw new Error('Can\'t merge Arguments from different EventEmitters'); } return new Argument( this.emitter, this.text + this.suffix + following.prefix + following.text, this.start, following.end, this.prefix, following.suffix); }, /** * See notes on events in Assignment. We might need to hook changes here * into a CliRequisition so they appear of the command line. */ setText: function(text) { if (text == null) { throw new Error('Illegal text for Argument: ' + text); } var ev = { argument: this, oldText: this.text, text: text }; this.text = text; this.emitter._dispatchEvent('argumentChange', ev); }, /** * Helper when we're putting arguments back together */ toString: function() { // TODO: There is a bug here - we should re-escape escaped characters // But can we do that reliably? return this.prefix + this.text + this.suffix; } }; /** * Merge an array of arguments into a single argument. * All Arguments in the array are expected to have the same emitter */ Argument.merge = function(argArray, start, end) { start = (start === undefined) ? 0 : start; end = (end === undefined) ? argArray.length : end; var joined; for (var i = start; i < end; i++) { var arg = argArray[i]; if (!joined) { joined = arg; } else { joined = joined.merge(arg); } } return joined; }; /** * We sometimes need a way to say 'this error occurs where ever the cursor is' */ Argument.AT_CURSOR = -1; /** * A link between a parameter and the data for that parameter. * The data for the parameter is available as in the preferred type and as * an Argument for the CLI. *

We also record validity information where applicable. *

For values, null and undefined have distinct definitions. null means * that a value has been provided, undefined means that it has not. * Thus, null is a valid default value, and common because it identifies an * parameter that is optional. undefined means there is no value from * the command line. * @constructor */ function Assignment(param, requisition) { this.param = param; this.requisition = requisition; this.setValue(param.defaultValue); }; Assignment.prototype = { /** * The parameter that we are assigning to * @readonly */ param: undefined, /** * Report on the status of the last parse() conversion. * @see types.Conversion */ conversion: undefined, /** * The current value in a type as specified by param.type */ value: undefined, /** * The string version of the current value */ arg: undefined, /** * The current value (i.e. not the string representation) * Use setValue() to mutate */ value: undefined, setValue: function(value) { if (this.value === value) { return; } if (value === undefined) { this.value = this.param.defaultValue; this.conversion = this.param.getDefault ? this.param.getDefault() : this.param.type.getDefault(); this.arg = undefined; } else { this.value = value; this.conversion = undefined; var text = (value == null) ? '' : this.param.type.stringify(value); if (this.arg) { this.arg.setText(text); } } this.requisition._assignmentChanged(this); }, /** * The textual representation of the current value * Use setValue() to mutate */ arg: undefined, setArgument: function(arg) { if (this.arg === arg) { return; } this.arg = arg; this.conversion = this.param.type.parse(arg.text); this.conversion.arg = arg; // TODO: make this automatic? this.value = this.conversion.value; this.requisition._assignmentChanged(this); }, /** * Create a list of the hints associated with this parameter assignment. * Generally there will be only one hint generated because we're currently * only displaying one hint at a time, ordering by distance from cursor * and severity. Since distance from cursor will be the same for all hints * from this assignment all but the most severe will ever be used. It might * make sense with more experience to alter this to function to be getHint() */ getHint: function() { // Allow the parameter to provide documentation if (this.param.getCustomHint && this.value && this.arg) { var hint = this.param.getCustomHint(this.value, this.arg); if (hint) { return hint; } } // If there is no argument, use the cursor position var message = '' + this.param.name + ': '; if (this.param.description) { // TODO: This should be a short description - do we need to trim? message += this.param.description.trim(); // Ensure the help text ends with '. ' if (message.charAt(message.length - 1) !== '.') { message += '.'; } if (message.charAt(message.length - 1) !== ' ') { message += ' '; } } var status = Status.VALID; var start = this.arg ? this.arg.start : Argument.AT_CURSOR; var end = this.arg ? this.arg.end : Argument.AT_CURSOR; var predictions; // Non-valid conversions will have useful information to pass on if (this.conversion) { status = this.conversion.status; if (this.conversion.message) { message += this.conversion.message; } predictions = this.conversion.predictions; } // Hint if the param is required, but not provided var argProvided = this.arg && this.arg.text !== ''; var dataProvided = this.value !== undefined || argProvided; if (this.param.defaultValue === undefined && !dataProvided) { status = Status.INVALID; message += 'Required<\strong>'; } return new Hint(status, message, start, end, predictions); }, /** * Basically setValue(conversion.predictions[0]) done in a safe * way. */ complete: function() { if (this.conversion && this.conversion.predictions && this.conversion.predictions.length > 0) { this.setValue(this.conversion.predictions[0]); } }, /** * If the cursor is at 'position', do we have sufficient data to start * displaying the next hint. This is both complex and important. * For example, if the user has just typed:

    *
  • 'set tabstop ' then they clearly want to know about the valid * values for the tabstop setting, so the hint is based on the next * parameter. *
  • 'set tabstop' (without trailing space) - they will probably still * want to know about the valid values for the tabstop setting because * there is no confusion about the setting in question. *
  • 'set tabsto' they've not finished typing a setting name so the hint * should be based on the current parameter. *
  • 'set tabstop' (when there is an additional tabstopstyle setting) we * can't make assumptions about the setting - we're not finished. *
*

Note that the input for 2 and 4 is identical, only the configuration * has changed, so hint display is environmental. * *

This function works out if the cursor is before the end of this * assignment (assuming that we've asked the same thing of the previous * assignment) and then attempts to work out if we should use the hint from * the next assignment even though technically the cursor is still inside * this one due to the rules above. */ isPositionCaptured: function(position) { if (!this.arg) { return false; } // Note we don't check if position >= this.arg.start because that's // implied by the fact that we're asking the assignments in turn, and // we want to avoid thing falling between the cracks, but we do need // to check that the argument does have a position if (this.arg.start === -1) { return false; } // We're clearly done if the position is past the end of the text if (position > this.arg.end) { return false; } // If we're AT the end, the position is captured if either the status // is not valid or if there are other valid options including current if (position === this.arg.end) { return this.conversion.status !== Status.VALID || this.conversion.predictions.length !== 0; } // Otherwise we're clearly inside return true; }, /** * Replace the current value with the lower value if such a concept * exists. */ decrement: function() { var replacement = this.param.type.decrement(this.value); if (replacement != null) { this.setValue(replacement); } }, /** * Replace the current value with the higher value if such a concept * exists. */ increment: function() { var replacement = this.param.type.increment(this.value); if (replacement != null) { this.setValue(replacement); } }, /** * Helper when we're rebuilding command lines. */ toString: function() { return this.arg ? this.arg.toString() : ''; } }; exports.Assignment = Assignment; /** * This is a special parameter to reflect the command itself. */ var commandParam = { name: '__command', type: 'command', description: 'The command to execute', /** * Provide some documentation for a command. */ getCustomHint: function(command, arg) { var docs = []; docs.push(' > '); docs.push(command.name); if (command.params && command.params.length > 0) { command.params.forEach(function(param) { if (param.defaultValue === undefined) { docs.push(' [' + param.name + ']'); } else { docs.push(' [' + param.name + ']'); } }, this); } docs.push('
'); docs.push(command.description ? command.description : '(No description)'); docs.push('
'); if (command.params && command.params.length > 0) { docs.push('

    '); command.params.forEach(function(param) { docs.push('
  • '); docs.push('' + param.name + ': '); docs.push(param.description ? param.description : '(No description)'); if (param.defaultValue === undefined) { docs.push(' [Required]'); } else if (param.defaultValue === null) { docs.push(' [Optional]'); } else { docs.push(' [Default: ' + param.defaultValue + ']'); } docs.push('
  • '); }, this); docs.push('
'); } return new Hint(Status.VALID, docs.join(''), arg); } }; /** * A Requisition collects the information needed to execute a command. * There is no point in a requisition for parameter-less commands because there * is no information to collect. A Requisition is a collection of assignments * of values to parameters, each handled by an instance of Assignment. * CliRequisition adds functions for parsing input from a command line to this * class. *

Events

* We publish the following events:
    *
  • argumentChange: The text of some argument has changed. It is likely that * any UI component displaying this argument will need to be updated. (Note that * this event is actually published by the Argument itself - see the docs for * Argument for more details) * The event object looks like: { argument: A, oldText: B, text: B } *
  • commandChange: The command has changed. It is likely that a UI * structure will need updating to match the parameters of the new command. * The event object looks like { command: A } * @constructor */ function Requisition(env) { this.env = env; this.commandAssignment = new Assignment(commandParam, this); } Requisition.prototype = { /** * The command that we are about to execute. * @see setCommandConversion() * @readonly */ commandAssignment: undefined, /** * The count of assignments. Excludes the commandAssignment * @readonly */ assignmentCount: undefined, /** * The object that stores of Assignment objects that we are filling out. * The Assignment objects are stored under their param.name for named * lookup. Note: We make use of the property of Javascript objects that * they are not just hashmaps, but linked-list hashmaps which iterate in * insertion order. * Excludes the commandAssignment. */ _assignments: undefined, /** * The store of hints generated by the assignments. We are trying to prevent * the UI from needing to access this in broad form, but instead use * methods that query part of this structure. */ _hints: undefined, /** * When the command changes, we need to keep a bunch of stuff in sync */ _assignmentChanged: function(assignment) { // This is all about re-creating Assignments if (assignment.param.name !== '__command') { return; } this._assignments = {}; if (assignment.value) { assignment.value.params.forEach(function(param) { this._assignments[param.name] = new Assignment(param, this); }, this); } this.assignmentCount = Object.keys(this._assignments).length; this._dispatchEvent('commandChange', { command: assignment.value }); }, /** * Assignments have an order, so we need to store them in an array. * But we also need named access ... */ getAssignment: function(nameOrNumber) { var name = (typeof nameOrNumber === 'string') ? nameOrNumber : Object.keys(this._assignments)[nameOrNumber]; return this._assignments[name]; }, /** * Where parameter name == assignment names - they are the same. */ getParameterNames: function() { return Object.keys(this._assignments); }, /** * A *shallow* clone of the assignments. * This is useful for systems that wish to go over all the assignments * finding values one way or another and wish to trim an array as they go. */ cloneAssignments: function() { return Object.keys(this._assignments).map(function(name) { return this._assignments[name]; }, this); }, /** * Collect the statuses from the Assignments. * The hints returned are sorted by severity */ _updateHints: function() { // TODO: work out when to clear this out for the plain Requisition case // this._hints = []; this.getAssignments(true).forEach(function(assignment) { this._hints.push(assignment.getHint()); }, this); Hint.sort(this._hints); // We would like to put some initial help here, but for anyone but // a complete novice a 'type help' message is very annoying, so we // need to find a way to only display this message once, or for // until the user click a 'close' button or similar // TODO: Add special case for '' input }, /** * Returns the most severe status */ getWorstHint: function() { return this._hints[0]; }, /** * Extract the names and values of all the assignments, and return as * an object. */ getArgsObject: function() { var args = {}; this.getAssignments().forEach(function(assignment) { args[assignment.param.name] = assignment.value; }, this); return args; }, /** * Access the arguments as an array. * @param includeCommand By default only the parameter arguments are * returned unless (includeCommand === true), in which case the list is * prepended with commandAssignment.arg */ getAssignments: function(includeCommand) { var args = []; if (includeCommand === true) { args.push(this.commandAssignment); } Object.keys(this._assignments).forEach(function(name) { args.push(this.getAssignment(name)); }, this); return args; }, /** * Reset all the assignments to their default values */ setDefaultValues: function() { this.getAssignments().forEach(function(assignment) { assignment.setValue(undefined); }, this); }, /** * Helper to call canon.exec */ exec: function() { canon.exec(this.commandAssignment.value, this.env, "cli", this.getArgsObject(), this.toCanonicalString()); }, /** * Extract a canonical version of the input */ toCanonicalString: function() { var line = []; line.push(this.commandAssignment.value.name); Object.keys(this._assignments).forEach(function(name) { var assignment = this._assignments[name]; var type = assignment.param.type; // TODO: This will cause problems if there is a non-default value // after a default value. Also we need to decide when to use // named parameters in place of positional params. Both can wait. if (assignment.value !== assignment.param.defaultValue) { line.push(' '); line.push(type.stringify(assignment.value)); } }, this); return line.join(''); } }; oop.implement(Requisition.prototype, EventEmitter); exports.Requisition = Requisition; /** * An object used during command line parsing to hold the various intermediate * data steps. *

    The 'output' of the update is held in 2 objects: input.hints which is an * array of hints to display to the user. In the future this will become a * single value. *

    The other output value is input.requisition which gives access to an * args object for use in executing the final command. * *

    The majority of the functions in this class are called in sequence by the * constructor. Their task is to add to hints fill out the requisition. *

    The general sequence is:

      *
    • _tokenize(): convert _typed into _parts *
    • _split(): convert _parts into _command and _unparsedArgs *
    • _assign(): convert _unparsedArgs into requisition *
    * * @param typed {string} The instruction as typed by the user so far * @param options {object} A list of optional named parameters. Can be any of: * flags: Flags for us to check against the predicates specified with the * commands. Defaulted to keyboard.buildFlags({ }); * if not specified. * @constructor */ function CliRequisition(env, options) { Requisition.call(this, env); if (options && options.flags) { /** * TODO: We were using a default of keyboard.buildFlags({ }); * This allowed us to have commands that only existed in certain contexts * - i.e. Javascript specific commands. */ this.flags = options.flags; } } oop.inherits(CliRequisition, Requisition); (function() { /** * Called by the UI when ever the user interacts with a command line input * @param input A structure that details the state of the input field. * It should look something like: { typed:a, cursor: { start:b, end:c } } * Where a is the contents of the input field, and b and c are the start * and end of the cursor/selection respectively. */ CliRequisition.prototype.update = function(input) { this.input = input; this._hints = []; var args = this._tokenize(input.typed); this._split(args); if (this.commandAssignment.value) { this._assign(args); } this._updateHints(); }; /** * Return an array of Status scores so we can create a marked up * version of the command line input. */ CliRequisition.prototype.getInputStatusMarkup = function() { // 'scores' is an array which tells us what chars are errors // Initialize with everything VALID var scores = this.toString().split('').map(function(ch) { return Status.VALID; }); // For all chars in all hints, check and upgrade the score this._hints.forEach(function(hint) { for (var i = hint.start; i <= hint.end; i++) { if (hint.status > scores[i]) { scores[i] = hint.status; } } }, this); return scores; }; /** * Reconstitute the input from the args */ CliRequisition.prototype.toString = function() { return this.getAssignments(true).map(function(assignment) { return assignment.toString(); }, this).join(''); }; var superUpdateHints = CliRequisition.prototype._updateHints; /** * Marks up hints in a number of ways: * - Makes INCOMPLETE hints that are not near the cursor INVALID since * they can't be completed by typing * - Finds the most severe hint, and annotates the array with it * - Finds the hint to display, and also annotates the array with it * TODO: I'm wondering if array annotation is evil and we should replace * this with an object. Need to find out more. */ CliRequisition.prototype._updateHints = function() { superUpdateHints.call(this); // Not knowing about cursor positioning, the requisition and assignments // can't know this, but anything they mark as INCOMPLETE is actually // INVALID unless the cursor is actually inside that argument. var c = this.input.cursor; this._hints.forEach(function(hint) { var startInHint = c.start >= hint.start && c.start <= hint.end; var endInHint = c.end >= hint.start && c.end <= hint.end; var inHint = startInHint || endInHint; if (!inHint && hint.status === Status.INCOMPLETE) { hint.status = Status.INVALID; } }, this); Hint.sort(this._hints); }; /** * Accessor for the hints array. * While we could just use the hints property, using getHints() is * preferred for symmetry with Requisition where it needs a function due to * lack of an atomic update system. */ CliRequisition.prototype.getHints = function() { return this._hints; }; /** * Look through the arguments attached to our assignments for the assignment * at the given position. */ CliRequisition.prototype.getAssignmentAt = function(position) { var assignments = this.getAssignments(true); for (var i = 0; i < assignments.length; i++) { var assignment = assignments[i]; if (!assignment.arg) { // There is no argument in this assignment, we've fallen off // the end of the obvious answers - it must be this one. return assignment; } if (assignment.isPositionCaptured(position)) { return assignment; } } return assignment; }; /** * Split up the input taking into account ' and " */ CliRequisition.prototype._tokenize = function(typed) { // For blank input, place a dummy empty argument into the list if (typed == null || typed.length === 0) { return [ new Argument(this, '', 0, 0, '', '') ]; } var OUTSIDE = 1; // The last character was whitespace var IN_SIMPLE = 2; // The last character was part of a parameter var IN_SINGLE_Q = 3; // We're inside a single quote: ' var IN_DOUBLE_Q = 4; // We're inside double quotes: " var mode = OUTSIDE; // First we un-escape. This list was taken from: // https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Core_Language_Features#Unicode // We are generally converting to their real values except for \', \" // and '\ ' which we are converting to unicode private characters so we // can distinguish them from ', " and ' ', which have special meaning. // They need swapping back post-split - see unescape2() typed = typed .replace(/\\\\/g, '\\') .replace(/\\b/g, '\b') .replace(/\\f/g, '\f') .replace(/\\n/g, '\n') .replace(/\\r/g, '\r') .replace(/\\t/g, '\t') .replace(/\\v/g, '\v') .replace(/\\n/g, '\n') .replace(/\\r/g, '\r') .replace(/\\ /g, '\uF000') .replace(/\\'/g, '\uF001') .replace(/\\"/g, '\uF002'); function unescape2(str) { return str .replace(/\uF000/g, ' ') .replace(/\uF001/g, '\'') .replace(/\uF002/g, '"'); } var i = 0; var start = 0; // Where did this section start? var prefix = ''; var args = []; while (true) { if (i >= typed.length) { // There is nothing else to read - tidy up if (mode !== OUTSIDE) { var str = unescape2(typed.substring(start, i)); args.push(new Argument(this, str, start, i, prefix, '')); } else { if (i !== start) { // There's a bunch of whitespace at the end of the // command add it to the last argument's suffix, // creating an empty argument if needed. var extra = typed.substring(start, i); var lastArg = args[args.length - 1]; if (!lastArg) { lastArg = new Argument(this, '', i, i, extra, ''); args.push(lastArg); } else { lastArg.suffix += extra; } } } break; } var c = typed[i]; switch (mode) { case OUTSIDE: if (c === '\'') { prefix = typed.substring(start, i + 1); mode = IN_SINGLE_Q; start = i + 1; } else if (c === '"') { prefix = typed.substring(start, i + 1); mode = IN_DOUBLE_Q; start = i + 1; } else if (/ /.test(c)) { // Still whitespace, do nothing } else { prefix = typed.substring(start, i); mode = IN_SIMPLE; start = i; } break; case IN_SIMPLE: // There is an edge case of xx'xx which we are assuming to // be a single parameter (and same with ") if (c === ' ') { var str = unescape2(typed.substring(start, i)); args.push(new Argument(this, str, start, i, prefix, '')); mode = OUTSIDE; start = i; prefix = ''; } break; case IN_SINGLE_Q: if (c === '\'') { var str = unescape2(typed.substring(start, i)); args.push(new Argument(this, str, start - 1, i + 1, prefix, c)); mode = OUTSIDE; start = i + 1; prefix = ''; } break; case IN_DOUBLE_Q: if (c === '"') { var str = unescape2(typed.substring(start, i)); args.push(new Argument(this, str, start - 1, i + 1, prefix, c)); mode = OUTSIDE; start = i + 1; prefix = ''; } break; } i++; } return args; }; /** * Looks in the canon for a command extension that matches what has been * typed at the command line. */ CliRequisition.prototype._split = function(args) { var argsUsed = 1; var arg; while (argsUsed <= args.length) { var arg = Argument.merge(args, 0, argsUsed); this.commandAssignment.setArgument(arg); if (!this.commandAssignment.value) { // Not found. break with value == null break; } /* // Previously we needed a way to hide commands depending context. // We have not resurrected that feature yet. if (!keyboard.flagsMatch(command.predicates, this.flags)) { // If the predicates say 'no match' then go LA LA LA command = null; break; } */ if (this.commandAssignment.value.exec) { // Valid command, break with command valid for (var i = 0; i < argsUsed; i++) { args.shift(); } break; } argsUsed++; } }; /** * Work out which arguments are applicable to which parameters. *

    This takes #_command.params and #_unparsedArgs and creates a map of * param names to 'assignment' objects, which have the following properties: *

      *
    • param - The matching parameter. *
    • index - Zero based index into where the match came from on the input *
    • value - The matching input *
    */ CliRequisition.prototype._assign = function(args) { if (args.length === 0) { this.setDefaultValues(); return; } // Create an error if the command does not take parameters, but we have // been given them ... if (this.assignmentCount === 0) { // TODO: previously we were doing some extra work to avoid this if // we determined that we had args that were all whitespace, but // probably given our tighter tokenize() this won't be an issue? this._hints.push(new Hint(Status.INVALID, this.commandAssignment.value.name + ' does not take any parameters', Argument.merge(args))); return; } // Special case: if there is only 1 parameter, and that's of type // text we put all the params into the first param if (this.assignmentCount === 1) { var assignment = this.getAssignment(0); if (assignment.param.type.name === 'text') { assignment.setArgument(Argument.merge(args)); return; } } var assignments = this.cloneAssignments(); var names = this.getParameterNames(); // Extract all the named parameters var used = []; assignments.forEach(function(assignment) { var namedArgText = '--' + assignment.name; var i = 0; while (true) { var arg = args[i]; if (namedArgText !== arg.text) { i++; if (i >= args.length) { break; } continue; } // boolean parameters don't have values, default to false if (assignment.param.type.name === 'boolean') { assignment.setValue(true); } else { if (i + 1 < args.length) { // Missing value portion of this named param this._hints.push(new Hint(Status.INCOMPLETE, 'Missing value for: ' + namedArgText, args[i])); } else { args.splice(i + 1, 1); assignment.setArgument(args[i + 1]); } } lang.arrayRemove(names, assignment.name); args.splice(i, 1); // We don't need to i++ if we splice } }, this); // What's left are positional parameters assign in order names.forEach(function(name) { var assignment = this.getAssignment(name); if (args.length === 0) { // No more values assignment.setValue(undefined); // i.e. default } else { var arg = args[0]; args.splice(0, 1); assignment.setArgument(arg); } }, this); if (args.length > 0) { var remaining = Argument.merge(args); this._hints.push(new Hint(Status.INVALID, 'Input \'' + remaining.text + '\' makes no sense.', remaining)); } }; })(); exports.CliRequisition = CliRequisition; }); /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Mozilla Skywriter. * * The Initial Developer of the Original Code is * Mozilla. * Portions created by the Initial Developer are Copyright (C) 2009 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Joe Walker (jwalker@mozilla.com) * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('cockpit/ui/settings', function(require, exports, module) { var types = require("pilot/types"); var SelectionType = require('pilot/types/basic').SelectionType; var direction = new SelectionType({ name: 'direction', data: [ 'above', 'below' ] }); var hintDirectionSetting = { name: "hintDirection", description: "Are hints shown above or below the command line?", type: "direction", defaultValue: "above" }; var outputDirectionSetting = { name: "outputDirection", description: "Is the output window shown above or below the command line?", type: "direction", defaultValue: "above" }; var outputHeightSetting = { name: "outputHeight", description: "What height should the output panel be?", type: "number", defaultValue: 300 }; exports.startup = function(data, reason) { types.registerType(direction); data.env.settings.addSetting(hintDirectionSetting); data.env.settings.addSetting(outputDirectionSetting); data.env.settings.addSetting(outputHeightSetting); }; exports.shutdown = function(data, reason) { types.unregisterType(direction); data.env.settings.removeSetting(hintDirectionSetting); data.env.settings.removeSetting(outputDirectionSetting); data.env.settings.removeSetting(outputHeightSetting); }; }); /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Skywriter. * * The Initial Developer of the Original Code is * Mozilla. * Portions created by the Initial Developer are Copyright (C) 2009 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Joe Walker (jwalker@mozilla.com) * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('cockpit/ui/cli_view', function(require, exports, module) { var editorCss = require("text!cockpit/ui/cli_view.css"); var event = require("pilot/event"); var dom = require("pilot/dom"); dom.importCssString(editorCss); var event = require("pilot/event"); var keys = require("pilot/keys"); var canon = require("pilot/canon"); var Status = require('pilot/types').Status; var CliRequisition = require('cockpit/cli').CliRequisition; var Hint = require('cockpit/cli').Hint; var RequestView = require('cockpit/ui/request_view').RequestView; var NO_HINT = new Hint(Status.VALID, '', 0, 0); /** * On startup we need to: * 1. Add 3 sets of elements to the DOM for: * - command line output * - input hints * - completion * 2. Attach a set of events so the command line works */ exports.startup = function(data, reason) { var cli = new CliRequisition(data.env); var cliView = new CliView(cli, data.env); data.env.cli = cli; }; /** * A class to handle the simplest UI implementation */ function CliView(cli, env) { cli.cliView = this; this.cli = cli; this.doc = document; this.win = dom.getParentWindow(this.doc); this.env = env; // TODO: we should have a better way to specify command lines??? this.element = this.doc.getElementById('cockpitInput'); if (!this.element) { // console.log('No element with an id of cockpit. Bailing on cli'); return; } this.settings = env.settings; this.hintDirection = this.settings.getSetting('hintDirection'); this.outputDirection = this.settings.getSetting('outputDirection'); this.outputHeight = this.settings.getSetting('outputHeight'); // If the requisition tells us something has changed, we use this to know // if we should ignore it this.isUpdating = false; this.createElements(); this.update(); } CliView.prototype = { /** * Create divs for completion, hints and output */ createElements: function() { var input = this.element; this.element.spellcheck = false; this.output = this.doc.getElementById('cockpitOutput'); this.popupOutput = (this.output == null); if (!this.output) { this.output = this.doc.createElement('div'); this.output.id = 'cockpitOutput'; this.output.className = 'cptOutput'; input.parentNode.insertBefore(this.output, input.nextSibling); var setMaxOutputHeight = function() { this.output.style.maxHeight = this.outputHeight.get() + 'px'; }.bind(this); this.outputHeight.addEventListener('change', setMaxOutputHeight); setMaxOutputHeight(); } this.completer = this.doc.createElement('div'); this.completer.className = 'cptCompletion VALID'; this.completer.style.color = dom.computedStyle(input, "color"); this.completer.style.fontSize = dom.computedStyle(input, "fontSize"); this.completer.style.fontFamily = dom.computedStyle(input, "fontFamily"); this.completer.style.fontWeight = dom.computedStyle(input, "fontWeight"); this.completer.style.fontStyle = dom.computedStyle(input, "fontStyle"); input.parentNode.insertBefore(this.completer, input.nextSibling); // Transfer background styling to the completer. this.completer.style.backgroundColor = input.style.backgroundColor; input.style.backgroundColor = 'transparent'; this.hinter = this.doc.createElement('div'); this.hinter.className = 'cptHints'; input.parentNode.insertBefore(this.hinter, input.nextSibling); var resizer = this.resizer.bind(this); event.addListener(this.win, 'resize', resizer); this.hintDirection.addEventListener('change', resizer); this.outputDirection.addEventListener('change', resizer); resizer(); canon.addEventListener('output', function(ev) { new RequestView(ev.request, this); }.bind(this)); event.addCommandKeyListener(input, this.onCommandKey.bind(this)); event.addListener(input, 'keyup', this.onKeyUp.bind(this)); // cursor position affects hint severity. TODO: shortcuts for speed event.addListener(input, 'mouseup', function(ev) { this.isUpdating = true; this.update(); this.isUpdating = false; }.bind(this)); this.cli.addEventListener('argumentChange', this.onArgChange.bind(this)); event.addListener(input, "focus", function() { dom.addCssClass(this.output, "cptFocusPopup"); dom.addCssClass(this.hinter, "cptFocusPopup"); }.bind(this)); function hideOutput() { dom.removeCssClass(this.output, "cptFocusPopup"); dom.removeCssClass(this.hinter, "cptFocusPopup"); }; event.addListener(input, "blur", hideOutput.bind(this)); hideOutput.call(this); }, /** * We need to see the output of the latest command entered */ scrollOutputToBottom: function() { // Certain browsers have a bug such that scrollHeight is too small // when content does not fill the client area of the element var scrollHeight = Math.max(this.output.scrollHeight, this.output.clientHeight); this.output.scrollTop = scrollHeight - this.output.clientHeight; }, /** * To be called on window resize or any time we want to align the elements * with the input box. */ resizer: function() { var rect = this.element.getClientRects()[0]; this.completer.style.top = rect.top + 'px'; var height = rect.bottom - rect.top; this.completer.style.height = height + 'px'; this.completer.style.lineHeight = height + 'px'; this.completer.style.left = rect.left + 'px'; var width = rect.right - rect.left; this.completer.style.width = width + 'px'; if (this.hintDirection.get() === 'below') { this.hinter.style.top = rect.bottom + 'px'; this.hinter.style.bottom = 'auto'; } else { this.hinter.style.top = 'auto'; this.hinter.style.bottom = (this.doc.documentElement.clientHeight - rect.top) + 'px'; } this.hinter.style.left = (rect.left + 30) + 'px'; this.hinter.style.maxWidth = (width - 110) + 'px'; if (this.popupOutput) { if (this.outputDirection.get() === 'below') { this.output.style.top = rect.bottom + 'px'; this.output.style.bottom = 'auto'; } else { this.output.style.top = 'auto'; this.output.style.bottom = (this.doc.documentElement.clientHeight - rect.top) + 'px'; } this.output.style.left = rect.left + 'px'; this.output.style.width = (width - 80) + 'px'; } }, /** * Ensure that TAB isn't handled by the browser */ onCommandKey: function(ev, hashId, keyCode) { var stopEvent; if (keyCode === keys.TAB || keyCode === keys.UP || keyCode === keys.DOWN) { stopEvent = true; } else if (hashId != 0 || keyCode != 0) { stopEvent = canon.execKeyCommand(this.env, 'cli', hashId, keyCode); } stopEvent && event.stopEvent(ev); }, /** * The main keyboard processing loop */ onKeyUp: function(ev) { var handled; /* var handled = keyboardManager.processKeyEvent(ev, this, { isCommandLine: true, isKeyUp: true }); */ // RETURN does a special exec/highlight thing if (ev.keyCode === keys.RETURN) { var worst = this.cli.getWorstHint(); // Deny RETURN unless the command might work if (worst.status === Status.VALID) { this.cli.exec(); this.element.value = ''; } else { // If we've denied RETURN because the command was not VALID, // select the part of the command line that is causing problems // TODO: if there are 2 errors are we picking the right one? dom.setSelectionStart(this.element, worst.start); dom.setSelectionEnd(this.element, worst.end); } } this.update(); // Special actions which delegate to the assignment var current = this.cli.getAssignmentAt(dom.getSelectionStart(this.element)); if (current) { // TAB does a special complete thing if (ev.keyCode === keys.TAB) { current.complete(); this.update(); } // UP/DOWN look for some history if (ev.keyCode === keys.UP) { current.increment(); this.update(); } if (ev.keyCode === keys.DOWN) { current.decrement(); this.update(); } } return handled; }, /** * Actually parse the input and make sure we're all up to date */ update: function() { this.isUpdating = true; var input = { typed: this.element.value, cursor: { start: dom.getSelectionStart(this.element), end: dom.getSelectionEnd(this.element.selectionEnd) } }; this.cli.update(input); var display = this.cli.getAssignmentAt(input.cursor.start).getHint(); // 1. Update the completer with prompt/error marker/TAB info dom.removeCssClass(this.completer, Status.VALID.toString()); dom.removeCssClass(this.completer, Status.INCOMPLETE.toString()); dom.removeCssClass(this.completer, Status.INVALID.toString()); var completion = '> '; if (this.element.value.length > 0) { var scores = this.cli.getInputStatusMarkup(); completion += this.markupStatusScore(scores); } // Display the "-> prediction" at the end of the completer if (this.element.value.length > 0 && display.predictions && display.predictions.length > 0) { var tab = display.predictions[0]; completion += '  ⇥ ' + (tab.name ? tab.name : tab); } this.completer.innerHTML = completion; dom.addCssClass(this.completer, this.cli.getWorstHint().status.toString()); // 2. Update the hint element var hint = ''; if (this.element.value.length !== 0) { hint += display.message; if (display.predictions && display.predictions.length > 0) { hint += ': [ '; display.predictions.forEach(function(prediction) { hint += (prediction.name ? prediction.name : prediction); hint += ' | '; }, this); hint = hint.replace(/\| $/, ']'); } } this.hinter.innerHTML = hint; if (hint.length === 0) { dom.addCssClass(this.hinter, 'cptNoPopup'); } else { dom.removeCssClass(this.hinter, 'cptNoPopup'); } this.isUpdating = false; }, /** * Markup an array of Status values with spans */ markupStatusScore: function(scores) { var completion = ''; // Create mark-up var i = 0; var lastStatus = -1; while (true) { if (lastStatus !== scores[i]) { completion += ''; lastStatus = scores[i]; } completion += this.element.value[i]; i++; if (i === this.element.value.length) { completion += ''; break; } if (lastStatus !== scores[i]) { completion += ''; } } return completion; }, /** * Update the input element to reflect the changed argument */ onArgChange: function(ev) { if (this.isUpdating) { return; } var prefix = this.element.value.substring(0, ev.argument.start); var suffix = this.element.value.substring(ev.argument.end); var insert = typeof ev.text === 'string' ? ev.text : ev.text.name; this.element.value = prefix + insert + suffix; // Fix the cursor. var insertEnd = (prefix + insert).length; this.element.selectionStart = insertEnd; this.element.selectionEnd = insertEnd; } }; exports.CliView = CliView; }); /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Skywriter. * * The Initial Developer of the Original Code is * Mozilla. * Portions created by the Initial Developer are Copyright (C) 2009 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Joe Walker (jwalker@mozilla.com) * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('cockpit/ui/request_view', function(require, exports, module) { var dom = require("pilot/dom"); var event = require("pilot/event"); var requestViewHtml = require("text!cockpit/ui/request_view.html"); var Templater = require("pilot/domtemplate").Templater; var requestViewCss = require("text!cockpit/ui/request_view.css"); dom.importCssString(requestViewCss); /** * Pull the HTML into the DOM, but don't add it to the document */ var templates = document.createElement('div'); templates.innerHTML = requestViewHtml; var row = templates.querySelector('.cptRow'); /** * Work out the path for images. * TODO: This should probably live in some utility area somewhere */ function imageUrl(path) { var dataUrl; try { dataUrl = require('text!cockpit/ui/' + path); } catch (e) { } if (dataUrl) { return dataUrl; } var filename = module.id.split('/').pop() + '.js'; var imagePath; if (module.uri.substr(-filename.length) !== filename) { console.error('Can\'t work out path from module.uri/module.id'); return path; } if (module.uri) { var end = module.uri.length - filename.length - 1; return module.uri.substr(0, end) + "/" + path; } return filename + path; } /** * Adds a row to the CLI output display */ function RequestView(request, cliView) { this.request = request; this.cliView = cliView; this.imageUrl = imageUrl; // Elements attached to this by the templater. For info only this.rowin = null; this.rowout = null; this.output = null; this.hide = null; this.show = null; this.duration = null; this.throb = null; new Templater().processNode(row.cloneNode(true), this); this.cliView.output.appendChild(this.rowin); this.cliView.output.appendChild(this.rowout); this.request.addEventListener('output', this.onRequestChange.bind(this)); }; RequestView.prototype = { /** * A single click on an invocation line in the console copies the command to * the command line */ copyToInput: function() { this.cliView.element.value = this.request.typed; }, /** * A double click on an invocation line in the console executes the command */ executeRequest: function(ev) { this.cliView.cli.update({ typed: this.request.typed, cursor: { start:0, end:0 } }); this.cliView.cli.exec(); }, hideOutput: function(ev) { this.output.style.display = 'none'; dom.addCssClass(this.hide, 'cmd_hidden'); dom.removeCssClass(this.show, 'cmd_hidden'); event.stopPropagation(ev); }, showOutput: function(ev) { this.output.style.display = 'block'; dom.removeCssClass(this.hide, 'cmd_hidden'); dom.addCssClass(this.show, 'cmd_hidden'); event.stopPropagation(ev); }, remove: function(ev) { this.cliView.output.removeChild(this.rowin); this.cliView.output.removeChild(this.rowout); event.stopPropagation(ev); }, onRequestChange: function(ev) { this.duration.innerHTML = this.request.duration ? 'completed in ' + (this.request.duration / 1000) + ' sec ' : ''; this.output.innerHTML = ''; this.request.outputs.forEach(function(output) { var node; if (typeof output == 'string') { node = document.createElement('p'); node.innerHTML = output; } else { node = output; } this.output.appendChild(node); }, this); this.cliView.scrollOutputToBottom(); dom.setCssClass(this.output, 'cmd_error', this.request.error); this.throb.style.display = this.request.completed ? 'none' : 'block'; } }; exports.RequestView = RequestView; }); /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is DomTemplate. * * The Initial Developer of the Original Code is Mozilla. * Portions created by the Initial Developer are Copyright (C) 2009 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Joe Walker (jwalker@mozilla.com) (original author) * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('pilot/domtemplate', function(require, exports, module) { // WARNING: do not 'use_strict' without reading the notes in envEval; /** * A templater that allows one to quickly template DOM nodes. */ function Templater() { this.scope = []; }; /** * Recursive function to walk the tree processing the attributes as it goes. * @param node the node to process. If you pass a string in instead of a DOM * element, it is assumed to be an id for use with document.getElementById() * @param data the data to use for node processing. */ Templater.prototype.processNode = function(node, data) { if (typeof node === 'string') { node = document.getElementById(node); } if (data === null || data === undefined) { data = {}; } this.scope.push(node.nodeName + (node.id ? '#' + node.id : '')); try { // Process attributes if (node.attributes && node.attributes.length) { // We need to handle 'foreach' and 'if' first because they might stop // some types of processing from happening, and foreach must come first // because it defines new data on which 'if' might depend. if (node.hasAttribute('foreach')) { this.processForEach(node, data); return; } if (node.hasAttribute('if')) { if (!this.processIf(node, data)) { return; } } // Only make the node available once we know it's not going away data.__element = node; // It's good to clean up the attributes when we've processed them, // but if we do it straight away, we mess up the array index var attrs = Array.prototype.slice.call(node.attributes); for (var i = 0; i < attrs.length; i++) { var value = attrs[i].value; var name = attrs[i].name; this.scope.push(name); try { if (name === 'save') { // Save attributes are a setter using the node value = this.stripBraces(value); this.property(value, data, node); node.removeAttribute('save'); } else if (name.substring(0, 2) === 'on') { // Event registration relies on property doing a bind value = this.stripBraces(value); var func = this.property(value, data); if (typeof func !== 'function') { this.handleError('Expected ' + value + ' to resolve to a function, but got ' + typeof func); } node.removeAttribute(name); var capture = node.hasAttribute('capture' + name.substring(2)); node.addEventListener(name.substring(2), func, capture); if (capture) { node.removeAttribute('capture' + name.substring(2)); } } else { // Replace references in all other attributes var self = this; var newValue = value.replace(/\$\{[^}]*\}/g, function(path) { return self.envEval(path.slice(2, -1), data, value); }); // Remove '_' prefix of attribute names so the DOM won't try // to use them before we've processed the template if (name.charAt(0) === '_') { node.removeAttribute(name); node.setAttribute(name.substring(1), newValue); } else if (value !== newValue) { attrs[i].value = newValue; } } } finally { this.scope.pop(); } } } // Loop through our children calling processNode. First clone them, so the // set of nodes that we visit will be unaffected by additions or removals. var childNodes = Array.prototype.slice.call(node.childNodes); for (var j = 0; j < childNodes.length; j++) { this.processNode(childNodes[j], data); } if (node.nodeType === Node.TEXT_NODE) { this.processTextNode(node, data); } } finally { this.scope.pop(); } }; /** * Handle * @param node An element with an 'if' attribute * @param data The data to use with envEval * @returns true if processing should continue, false otherwise */ Templater.prototype.processIf = function(node, data) { this.scope.push('if'); try { var originalValue = node.getAttribute('if'); var value = this.stripBraces(originalValue); var recurse = true; try { var reply = this.envEval(value, data, originalValue); recurse = !!reply; } catch (ex) { this.handleError('Error with \'' + value + '\'', ex); recurse = false; } if (!recurse) { node.parentNode.removeChild(node); } node.removeAttribute('if'); return recurse; } finally { this.scope.pop(); } }; /** * Handle and the special case of * * @param node An element with a 'foreach' attribute * @param data The data to use with envEval */ Templater.prototype.processForEach = function(node, data) { this.scope.push('foreach'); try { var originalValue = node.getAttribute('foreach'); var value = originalValue; var paramName = 'param'; if (value.charAt(0) === '$') { // No custom loop variable name. Use the default: 'param' value = this.stripBraces(value); } else { // Extract the loop variable name from 'NAME in ${ARRAY}' var nameArr = value.split(' in '); paramName = nameArr[0].trim(); value = this.stripBraces(nameArr[1].trim()); } node.removeAttribute('foreach'); try { var self = this; // Process a single iteration of a loop var processSingle = function(member, clone, ref) { ref.parentNode.insertBefore(clone, ref); data[paramName] = member; self.processNode(clone, data); delete data[paramName]; }; // processSingle is no good for nodes where we want to work on // the childNodes rather than the node itself var processAll = function(scope, member) { self.scope.push(scope); try { if (node.nodeName === 'LOOP') { for (var i = 0; i < node.childNodes.length; i++) { var clone = node.childNodes[i].cloneNode(true); processSingle(member, clone, node); } } else { var clone = node.cloneNode(true); clone.removeAttribute('foreach'); processSingle(member, clone, node); } } finally { self.scope.pop(); } }; var reply = this.envEval(value, data, originalValue); if (Array.isArray(reply)) { reply.forEach(function(data, i) { processAll('' + i, data); }, this); } else { for (var param in reply) { if (reply.hasOwnProperty(param)) { processAll(param, param); } } } node.parentNode.removeChild(node); } catch (ex) { this.handleError('Error with \'' + value + '\'', ex); } } finally { this.scope.pop(); } }; /** * Take a text node and replace it with another text node with the ${...} * sections parsed out. We replace the node by altering node.parentNode but * we could probably use a DOM Text API to achieve the same thing. * @param node The Text node to work on * @param data The data to use in calls to envEval */ Templater.prototype.processTextNode = function(node, data) { // Replace references in other attributes var value = node.data; // We can't use the string.replace() with function trick (see generic // attribute processing in processNode()) because we need to support // functions that return DOM nodes, so we can't have the conversion to a // string. // Instead we process the string as an array of parts. In order to split // the string up, we first replace '${' with '\uF001$' and '}' with '\uF002' // We can then split using \uF001 or \uF002 to get an array of strings // where scripts are prefixed with $. // \uF001 and \uF002 are just unicode chars reserved for private use. value = value.replace(/\$\{([^}]*)\}/g, '\uF001$$$1\uF002'); var parts = value.split(/\uF001|\uF002/); if (parts.length > 1) { parts.forEach(function(part) { if (part === null || part === undefined || part === '') { return; } if (part.charAt(0) === '$') { part = this.envEval(part.slice(1), data, node.data); } // It looks like this was done a few lines above but see envEval if (part === null) { part = "null"; } if (part === undefined) { part = "undefined"; } // if (isDOMElement(part)) { ... } if (typeof part.cloneNode !== 'function') { part = node.ownerDocument.createTextNode(part.toString()); } node.parentNode.insertBefore(part, node); }, this); node.parentNode.removeChild(node); } }; /** * Warn of string does not begin '${' and end '}' * @param str the string to check. * @return The string stripped of ${ and }, or untouched if it does not match */ Templater.prototype.stripBraces = function(str) { if (!str.match(/\$\{.*\}/g)) { this.handleError('Expected ' + str + ' to match ${...}'); return str; } return str.slice(2, -1); }; /** * Combined getter and setter that works with a path through some data set. * For example: *
      *
    • property('a.b', { a: { b: 99 }}); // returns 99 *
    • property('a', { a: { b: 99 }}); // returns { b: 99 } *
    • property('a', { a: { b: 99 }}, 42); // returns 99 and alters the * input data to be { a: { b: 42 }} *
    * @param path An array of strings indicating the path through the data, or * a string to be cut into an array using split('.') * @param data An object to look in for the path argument * @param newValue (optional) If defined, this value will replace the * original value for the data at the path specified. * @return The value pointed to by path before any * newValue is applied. */ Templater.prototype.property = function(path, data, newValue) { this.scope.push(path); try { if (typeof path === 'string') { path = path.split('.'); } var value = data[path[0]]; if (path.length === 1) { if (newValue !== undefined) { data[path[0]] = newValue; } if (typeof value === 'function') { return function() { return value.apply(data, arguments); }; } return value; } if (!value) { this.handleError('Can\'t find path=' + path); return null; } return this.property(path.slice(1), value, newValue); } finally { this.scope.pop(); } }; /** * Like eval, but that creates a context of the variables in env in * which the script is evaluated. * WARNING: This script uses 'with' which is generally regarded to be evil. * The alternative is to create a Function at runtime that takes X parameters * according to the X keys in the env object, and then call that function using * the values in the env object. This is likely to be slow, but workable. * @param script The string to be evaluated. * @param env The environment in which to eval the script. * @param context Optional debugging string in case of failure * @return The return value of the script, or the error message if the script * execution failed. */ Templater.prototype.envEval = function(script, env, context) { with (env) { try { this.scope.push(context); return eval(script); } catch (ex) { this.handleError('Template error evaluating \'' + script + '\'', ex); return script; } finally { this.scope.pop(); } } }; /** * A generic way of reporting errors, for easy overloading in different * environments. * @param message the error message to report. * @param ex optional associated exception. */ Templater.prototype.handleError = function(message, ex) { this.logError(message); this.logError('In: ' + this.scope.join(' > ')); if (ex) { this.logError(ex); } }; /** * A generic way of reporting errors, for easy overloading in different * environments. * @param message the error message to report. */ Templater.prototype.logError = function(message) { window.console && window.console.log && console.log(message); }; exports.Templater = Templater; }); /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Skywriter. * * The Initial Developer of the Original Code is * Mozilla. * Portions created by the Initial Developer are Copyright (C) 2009 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Skywriter Team (skywriter@mozilla.com) * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('cockpit/commands/basic', function(require, exports, module) { var canon = require('pilot/canon'); /** * '!' command */ var bangCommandSpec = { name: 'sh', description: 'Execute a system command (requires server support)', params: [ { name: 'command', type: 'text', description: 'The string to send to the os shell.' } ], exec: function(env, args, request) { var req = new XMLHttpRequest(); req.open('GET', '/exec?args=' + args.command, true); req.onreadystatechange = function(ev) { if (req.readyState == 4) { if (req.status == 200) { request.done('
    ' + req.responseText + '
    '); } } }; req.send(null); } }; var canon = require('pilot/canon'); exports.startup = function(data, reason) { canon.addCommand(bangCommandSpec); }; exports.shutdown = function(data, reason) { canon.removeCommand(bangCommandSpec); }; }); define("text!cockpit/ui/cli_view.css", "" + "#cockpitInput { padding-left: 16px; }" + "" + ".cptOutput { overflow: auto; position: absolute; z-index: 999; display: none; }" + "" + ".cptCompletion { padding: 0; position: absolute; z-index: -1000; }" + ".cptCompletion.VALID { background: #FFF; }" + ".cptCompletion.INCOMPLETE { background: #DDD; }" + ".cptCompletion.INVALID { background: #DDD; }" + ".cptCompletion span { color: #FFF; }" + ".cptCompletion span.INCOMPLETE { color: #DDD; border-bottom: 2px dotted #F80; }" + ".cptCompletion span.INVALID { color: #DDD; border-bottom: 2px dotted #F00; }" + "span.cptPrompt { color: #66F; font-weight: bold; }" + "" + "" + ".cptHints {" + " color: #000;" + " position: absolute;" + " border: 1px solid rgba(230, 230, 230, 0.8);" + " background: rgba(250, 250, 250, 0.8);" + " -moz-border-radius-topleft: 10px;" + " -moz-border-radius-topright: 10px;" + " border-top-left-radius: 10px; border-top-right-radius: 10px;" + " z-index: 1000;" + " padding: 8px;" + " display: none;" + "}" + "" + ".cptFocusPopup { display: block; }" + ".cptFocusPopup.cptNoPopup { display: none; }" + "" + ".cptHints ul { margin: 0; padding: 0 15px; }" + "" + ".cptGt { font-weight: bold; font-size: 120%; }" + ""); define("text!cockpit/ui/request_view.css", "" + ".cptRowIn {" + " display: box; display: -moz-box; display: -webkit-box;" + " box-orient: horizontal; -moz-box-orient: horizontal; -webkit-box-orient: horizontal;" + " box-align: center; -moz-box-align: center; -webkit-box-align: center;" + " color: #333;" + " background-color: #EEE;" + " width: 100%;" + " font-family: consolas, courier, monospace;" + "}" + ".cptRowIn > * { padding-left: 2px; padding-right: 2px; }" + ".cptRowIn > img { cursor: pointer; }" + ".cptHover { display: none; }" + ".cptRowIn:hover > .cptHover { display: block; }" + ".cptRowIn:hover > .cptHover.cptHidden { display: none; }" + ".cptOutTyped {" + " box-flex: 1; -moz-box-flex: 1; -webkit-box-flex: 1;" + " font-weight: bold; color: #000; font-size: 120%;" + "}" + ".cptRowOutput { padding-left: 10px; line-height: 1.2em; }" + ".cptRowOutput strong," + ".cptRowOutput b," + ".cptRowOutput th," + ".cptRowOutput h1," + ".cptRowOutput h2," + ".cptRowOutput h3 { color: #000; }" + ".cptRowOutput a { font-weight: bold; color: #666; text-decoration: none; }" + ".cptRowOutput a: hover { text-decoration: underline; cursor: pointer; }" + ".cptRowOutput input[type=password]," + ".cptRowOutput input[type=text]," + ".cptRowOutput textarea {" + " color: #000; font-size: 120%;" + " background: transparent; padding: 3px;" + " border-radius: 5px; -moz-border-radius: 5px; -webkit-border-radius: 5px;" + "}" + ".cptRowOutput table," + ".cptRowOutput td," + ".cptRowOutput th { border: 0; padding: 0 2px; }" + ".cptRowOutput .right { text-align: right; }" + ""); define("text!cockpit/ui/request_view.html", "" + "
    " + " " + "
    " + "" + " " + "
    >
    " + "
    ${request.typed}
    " + "" + " " + "
    " + " \"Hide" + " \"Show" + " \"Remove" + "" + "
    " + "" + " " + "
    " + "
    " + " " + "
    " + "
    " + ""); define("text!cockpit/ui/images/closer.png", "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAj9JREFUeNp0ks+LUlEUx7/vV1o8Z8wUx3IEHcQmiBiQlomjRNCiZpEuEqF/oEUwq/6EhvoHggmRcJUQBM1CRJAW0aLIaGQimZJxJsWxyV/P9/R1zzWlFl04vPvOPZ9z7rnnK5imidmKRCIq+zxgdoPZ1T/ut8xeM3tcKpW6s1hhBkaj0Qj7bDebTX+324WmadxvsVigqipcLleN/d4rFoulORiLxTZY8ItOp8MBCpYkiYPj8Xjus9vtlORWoVB4KcTjcQc732dLpSRXvCZaAws6Q4WDdqsO52kNH+oCRFGEz+f7ydwBKRgMPmTXi49GI1x2D/DsznesB06ws2eDbI7w9HYN6bVjvGss4KAjwDAMq81mM2SW5Wa/3weBbz42UL9uYnVpiO2Nr9ANHSGXib2Wgm9tCYIggGKJEVkvlwgi5/FQRmTLxO6hgJVzI1x0T/fJrBtHJxPeL6tI/fsZLA6ot8lkQi8HRVbw94gkWYI5MaHrOjcCGSNRxZosy9y5cErDzn0Dqx7gcwO8WtBp4PndI35GMYqiUMUvBL5yOBz8yRfFNpbPmqgcCFh/IuHa1nR/YXGM8+oUpFhihEQiwcdRLpfVRqOBtWXWq34Gra6AXq8Hp2piZcmKT4cKnE4nwuHwdByVSmWQz+d32WCTlHG/qaHHREN9kgi0sYQfv0R4PB4EAgESQDKXy72fSy6VSnHJVatVf71eR7vd5n66mtfrRSgU4pLLZrOlf7RKK51Ok8g3/yPyR5lMZi7y3wIMAME4EigHWgKnAAAAAElFTkSuQmCC"); define("text!cockpit/ui/images/dot_clear.gif", "data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAEBMgA7"); define("text!cockpit/ui/images/minus.png", "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAAZiS0dEANIA0gDS7KbF4AAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9kFGw4xMrIJw5EAAAHcSURBVCjPhZIxSxtxGMZ/976XhJA/RA5EAyJcFksnp64hjUPBoXRyCYLQTyD0UxScu0nFwalCQSgFCVk7dXAwUAiBDA2RO4W7yN1x9+9gcyhU+pteHt4H3pfncay1LOl0OgY4BN4Ar/7KP4BvwNFwOIyWu87S2O12O8DxfD73oygiSRIAarUaxhhWV1fHwMFgMBiWxl6v9y6Koi+3t7ckSUKtVkNVAcjzvNRWVlYwxry9vLz86uzs7HjAZDKZGGstjUaDfxHHMSLC5ubmHdB2VfVwNpuZ5clxHPMcRVFwc3PTXFtbO3RFZHexWJCmabnweAaoVqvlv4vFAhHZdVX1ZZqmOI5DURR8fz/lxbp9Yrz+7bD72SfPcwBU1XdF5N5aWy2KgqIoeBzPEnWVLMseYnAcRERdVR27rrsdxzGqyutP6898+GBsNBqo6i9XVS88z9sOggAR4X94noeqXoiIHPm+H9XrdYIgIAxDwjAkTVPCMESzBy3LMprNJr7v34nIkV5dXd2fn59fG2P2siwjSRIqlQrWWlSVJFcqlQqtVot2u40xZu/s7OxnWbl+v98BjkejkT+dTgmCoDxtY2ODra2tMXBweno6fNJVgP39fQN8eKbkH09OTsqS/wHFRdHPfTSfjwAAAABJRU5ErkJggg=="); define("text!cockpit/ui/images/pinaction.png", "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAC7mlDQ1BJQ0MgUHJvZmlsZQAAeAGFVM9rE0EU/jZuqdAiCFprDrJ4kCJJWatoRdQ2/RFiawzbH7ZFkGQzSdZuNuvuJrWliOTi0SreRe2hB/+AHnrwZC9KhVpFKN6rKGKhFy3xzW5MtqXqwM5+8943731vdt8ADXLSNPWABOQNx1KiEWlsfEJq/IgAjqIJQTQlVdvsTiQGQYNz+Xvn2HoPgVtWw3v7d7J3rZrStpoHhP1A4Eea2Sqw7xdxClkSAog836Epx3QI3+PY8uyPOU55eMG1Dys9xFkifEA1Lc5/TbhTzSXTQINIOJT1cVI+nNeLlNcdB2luZsbIEL1PkKa7zO6rYqGcTvYOkL2d9H5Os94+wiHCCxmtP0a4jZ71jNU/4mHhpObEhj0cGDX0+GAVtxqp+DXCFF8QTSeiVHHZLg3xmK79VvJKgnCQOMpkYYBzWkhP10xu+LqHBX0m1xOv4ndWUeF5jxNn3tTd70XaAq8wDh0MGgyaDUhQEEUEYZiwUECGPBoxNLJyPyOrBhuTezJ1JGq7dGJEsUF7Ntw9t1Gk3Tz+KCJxlEO1CJL8Qf4qr8lP5Xn5y1yw2Fb3lK2bmrry4DvF5Zm5Gh7X08jjc01efJXUdpNXR5aseXq8muwaP+xXlzHmgjWPxHOw+/EtX5XMlymMFMXjVfPqS4R1WjE3359sfzs94i7PLrXWc62JizdWm5dn/WpI++6qvJPmVflPXvXx/GfNxGPiKTEmdornIYmXxS7xkthLqwviYG3HCJ2VhinSbZH6JNVgYJq89S9dP1t4vUZ/DPVRlBnM0lSJ93/CKmQ0nbkOb/qP28f8F+T3iuefKAIvbODImbptU3HvEKFlpW5zrgIXv9F98LZua6N+OPwEWDyrFq1SNZ8gvAEcdod6HugpmNOWls05Uocsn5O66cpiUsxQ20NSUtcl12VLFrOZVWLpdtiZ0x1uHKE5QvfEp0plk/qv8RGw/bBS+fmsUtl+ThrWgZf6b8C8/UXAeIuJAAAACXBIWXMAAAsTAAALEwEAmpwYAAAClklEQVQ4EX1TXUhUQRQ+Z3Zmd+9uN1q2P3UpZaEwcikKekkqLKggKHJ96MHe9DmLkCDa9U198Id8kErICmIlRAN96UdE6QdBW/tBA5Uic7E0zN297L17p5mb1zYjD3eYc+d83zlnON8g5xzWNUSEdUBkHTJasRWySPP7fw3hfwkk2GoNsc0vOaJRHo1GV/GiMctkTIJRFlpZli8opK+htmf83gXeG63oteOtra0u25e7TYJIJELb26vYCACTgUe1lXV86BTn745l+MsyHqs53S/Aq4VEUa9Y6ko14eYY4u3AyM3HYwdKU35DZyblGR2+qq6W0X2Nnh07xynnVYpHORx/E1/GvvqaAZUayjMjdM2f/Lgr5E+fV93zR4u3zKCLughsZqKwAzAxaz6dPY6JgjLUF+eSP5OpjmAw2E8DvldHSvJMKPg08aRor1tc4BuALu6mOwGWdQC3mKIqRsC8mKd8wYfD78/earzSYzdMDW9QgKb0Is8CBY1mQXOiaXAHEpMDE5XTJqIq4EiyxUqKlpfkF0pyV1OTAoFAhmTmyCCoDsZNZvIkUjELQpipo0sQqYZAswZHwsEEE10M0pq2SSZY9HqNcDicJcNTpBvQJz40UbSOTh1B8bDpuY0w9Hb3kkn9lPAlBLfhfD39XTtX/blFJqiqrjbkTi63Hbofj2uL4GMsmzFgbDJ/vmMgv/lB4syJ0oXO7d3j++vio6GFsYmD6cHJreWc3/jRVVHhsOYvM8iZ36mtjPDBk/xDZE8CoHlbrlAssbTxDdDJvdb536L7I6S7Vy++6Gi4Xi9BsUthJRaLOYSPz4XALKI4j4iObd/e5UtDKUjZzYyYRyGAJv01Zj8kC5cbs5WY83hQnv0DzCXl+r8APElkq0RU6oMAAAAASUVORK5CYII="); define("text!cockpit/ui/images/pinin.png", "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAC7mlDQ1BJQ0MgUHJvZmlsZQAAeAGFVM9rE0EU/jZuqdAiCFprDrJ4kCJJWatoRdQ2/RFiawzbH7ZFkGQzSdZuNuvuJrWliOTi0SreRe2hB/+AHnrwZC9KhVpFKN6rKGKhFy3xzW5MtqXqwM5+8943731vdt8ADXLSNPWABOQNx1KiEWlsfEJq/IgAjqIJQTQlVdvsTiQGQYNz+Xvn2HoPgVtWw3v7d7J3rZrStpoHhP1A4Eea2Sqw7xdxClkSAog836Epx3QI3+PY8uyPOU55eMG1Dys9xFkifEA1Lc5/TbhTzSXTQINIOJT1cVI+nNeLlNcdB2luZsbIEL1PkKa7zO6rYqGcTvYOkL2d9H5Os94+wiHCCxmtP0a4jZ71jNU/4mHhpObEhj0cGDX0+GAVtxqp+DXCFF8QTSeiVHHZLg3xmK79VvJKgnCQOMpkYYBzWkhP10xu+LqHBX0m1xOv4ndWUeF5jxNn3tTd70XaAq8wDh0MGgyaDUhQEEUEYZiwUECGPBoxNLJyPyOrBhuTezJ1JGq7dGJEsUF7Ntw9t1Gk3Tz+KCJxlEO1CJL8Qf4qr8lP5Xn5y1yw2Fb3lK2bmrry4DvF5Zm5Gh7X08jjc01efJXUdpNXR5aseXq8muwaP+xXlzHmgjWPxHOw+/EtX5XMlymMFMXjVfPqS4R1WjE3359sfzs94i7PLrXWc62JizdWm5dn/WpI++6qvJPmVflPXvXx/GfNxGPiKTEmdornIYmXxS7xkthLqwviYG3HCJ2VhinSbZH6JNVgYJq89S9dP1t4vUZ/DPVRlBnM0lSJ93/CKmQ0nbkOb/qP28f8F+T3iuefKAIvbODImbptU3HvEKFlpW5zrgIXv9F98LZua6N+OPwEWDyrFq1SNZ8gvAEcdod6HugpmNOWls05Uocsn5O66cpiUsxQ20NSUtcl12VLFrOZVWLpdtiZ0x1uHKE5QvfEp0plk/qv8RGw/bBS+fmsUtl+ThrWgZf6b8C8/UXAeIuJAAAACXBIWXMAAAsTAAALEwEAmpwYAAABZ0lEQVQ4Ea2TPUsDQRCGZ89Eo4FACkULEQs1CH4Uamfjn7GxEYJFIFXgChFsbPwzNnZioREkaiHBQtEiEEiMRm/dZ8OEGAxR4sBxx877Pju7M2estTJIxLrNuVwuMxQEx0ZkzcFHyRtjXt02559RtB2GYanTYzoryOfz+6l4Nbszf2niwffKmpGRo9sVW22mDgqFwp5C2gDMm+P32a3JB1N+n5JifUGeP9JeNxGryPLYjcwMP8rJ07Q9fZltQzyAstOJ2vVu5sKc1ZZkRBrOcKeb+HexPidvkpCN5JUcllZtpZFc5DgBWc5M2eysZuMuofMBSA4NWjx4PUCsXefMlI0QY3ewRg4NWi4ZTQsgrjYXema+e4VqtEMK6KXvu+4B9Bklt90vVKMeD2BI6DOt4rZ/Gk7WyKFBi4fNPIAJY0joM61SCCZ9tI1o0OIB8D+DBIkYaJRbCBH9mZgNt+bb++ufSSF/eX8BYcDeAzuQJVUAAAAASUVORK5CYII="); define("text!cockpit/ui/images/pinout.png", "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAC7mlDQ1BJQ0MgUHJvZmlsZQAAeAGFVM9rE0EU/jZuqdAiCFprDrJ4kCJJWatoRdQ2/RFiawzbH7ZFkGQzSdZuNuvuJrWliOTi0SreRe2hB/+AHnrwZC9KhVpFKN6rKGKhFy3xzW5MtqXqwM5+8943731vdt8ADXLSNPWABOQNx1KiEWlsfEJq/IgAjqIJQTQlVdvsTiQGQYNz+Xvn2HoPgVtWw3v7d7J3rZrStpoHhP1A4Eea2Sqw7xdxClkSAog836Epx3QI3+PY8uyPOU55eMG1Dys9xFkifEA1Lc5/TbhTzSXTQINIOJT1cVI+nNeLlNcdB2luZsbIEL1PkKa7zO6rYqGcTvYOkL2d9H5Os94+wiHCCxmtP0a4jZ71jNU/4mHhpObEhj0cGDX0+GAVtxqp+DXCFF8QTSeiVHHZLg3xmK79VvJKgnCQOMpkYYBzWkhP10xu+LqHBX0m1xOv4ndWUeF5jxNn3tTd70XaAq8wDh0MGgyaDUhQEEUEYZiwUECGPBoxNLJyPyOrBhuTezJ1JGq7dGJEsUF7Ntw9t1Gk3Tz+KCJxlEO1CJL8Qf4qr8lP5Xn5y1yw2Fb3lK2bmrry4DvF5Zm5Gh7X08jjc01efJXUdpNXR5aseXq8muwaP+xXlzHmgjWPxHOw+/EtX5XMlymMFMXjVfPqS4R1WjE3359sfzs94i7PLrXWc62JizdWm5dn/WpI++6qvJPmVflPXvXx/GfNxGPiKTEmdornIYmXxS7xkthLqwviYG3HCJ2VhinSbZH6JNVgYJq89S9dP1t4vUZ/DPVRlBnM0lSJ93/CKmQ0nbkOb/qP28f8F+T3iuefKAIvbODImbptU3HvEKFlpW5zrgIXv9F98LZua6N+OPwEWDyrFq1SNZ8gvAEcdod6HugpmNOWls05Uocsn5O66cpiUsxQ20NSUtcl12VLFrOZVWLpdtiZ0x1uHKE5QvfEp0plk/qv8RGw/bBS+fmsUtl+ThrWgZf6b8C8/UXAeIuJAAAACXBIWXMAAAsTAAALEwEAmpwYAAACyUlEQVQ4EW1TXUgUURQ+Z3ZmnVV3QV2xJbVSEIowQbAfLQx8McLoYX2qjB58MRSkP3vZppceYhGxgrZaIughlYpE7CHFWiiKyj9II0qxWmwlNh1Xtp2f27mz7GDlZX7uuXO+73zfuXeQMQYIgAyALppgyBtse32stsw86txkHhATn+FbfPfzxnPB+vR3RMJYuTwW6bbB4a6WS5O3Yu2VlXIesDiAamiQNKVlVXfx5I0GJ7DY7p0/+erU4dgeMJIA31WNxZmAgibOreXDqF55sY4SFUURqbi+nkjgwTyAbHhLX8yOLsSM2QRA3JRAAgd4RGPbVhkKEp8qeJ7PFyW3fw++YHtC7CkaD0amqyqihSwlMQQ0wa07IjPVI/vbexreIUrVaQV2D4RMQ/o7m12Mdfx4H3PfB9FNzTR1U2cO0Bi45aV6xNvFBNaoIAfbSiwLlqi9/hR/R3Nrhua+Oqi9TEKiB02C7YXz+Pba4MTDrpbLiMAxNgmXb+HpwVkZdoIrkn9isW7nRw/TZYaagZArAWyhfqsSDL/c9aTx7JUjGZCtYExRqCzAwGblwr6aFQ84nTo6qZ7XCeCVQNckE/KSWolvoQnxeoFFgIh8G/nA+kBAxxuQO5m9eFrwLIGJHgcyM63VFMhRSgNVyJr7og8y1vbTQpH8DIEVgxuYuexw0QECIalq5FYgEmpkgoFYltU/lnrqDz5osirSFpF7lrHAFKSWHYfEs+mY/82UnAStyMlW8sUPsVIciTZgz3jV1ebg0CEOpgPF22s1z1YQYKSXPJ1hbAhR8T26WdLhkuVfAzPR+YO1Ox5n58SmCcF6e3uzAoHA77RkevJdWH/3+f2O9TGf3w3fWQ2Hw5F/13mcsWAT+vv6DK4kFApJ/d3d1k+kJtbCrmxXHS3n8ER6b3CQbAqaEHVra6sGxcXW4SovLx+empxapS//FfwD9kpMJjMMBBAAAAAASUVORK5CYII="); define("text!cockpit/ui/images/pins.png", "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAQCAYAAABQrvyxAAAACXBIWXMAAAsTAAALEwEAmpwYAAAGYklEQVRIDbVWe0yURxCf/R735o6DO0FBe0RFsaL4iLXGIKa2SY3P6JGa2GpjlJjUV9NosbU++tYUbEnaQIrVaKJBG7WiNFQFUWO1UUEsVg2CAgoeHHLewcH32O58cBdQsX9Y5+7LfrszOzO/2ZnZj1BKgTBiIwVGVvKd49OVVYunDlXn6wdBKh+ogXrv+DOz1melIb+3LM5fNv2XPYE5EHY+L3PJljN5zavHpJjsQNsA/JJEgyC2+WTjy3b0GfoJW8O4aoHtDwiHQrj5lw1LLyyb1bp5zAjJTus9klrVpdD6TqH2ngVO+0dsRJnp06cLIYU4fx7NnRI3bu7UIYOeJ/McnuY88q3k62gc0S4Dgf5qhICQtIXS2lqD7BhSduPk3YfyzXaANhBBJDxYdUqCywB2qS4RdyUuSkTF/VJxcbH5j8N7/75RuFrN3Zh8OS8zqf5m4UpPeenOyP42dbtBeuvVnCdkK1e4PfPouX03mo9se+c33M8wqDk5Ofqed8REUTicQhbySUxp9u3KlMSHTtrFU6Kyn03lz15PPpW25vsZeYSIKyiVURcqeZJOH9lTNZLfnxRjU/uwrjbEUBWsapcSO2Hq4k0VfZg9EzxdDNCEjDxgNqRDme9umz/btwlsHRIEePHgAf73RdnHZ6LTuIUBN7OBQ+c1Fdnp6cZ1BQUdeRuWZi97o3ktDQQkVeFFzqJARd1A5a0Vr7ta6Kp6TZjtZ+NTIOoKF6qDrL7e0QQIUCiqMMKk8Z1Q/SCSKvzocf2B6NEN0SQn/kTO6fKJ0zqjZUlQBSpJ0GjR77w0aoc1Pr6S5/kVJrNpakV5hR+LWKN4t7sLX+p0rx2vqSta64olIulUKUgCSXLWE1R4KPPSj+5vhm2hdDOG+CkQBmhhyyKq6SaFYWTn5bB3QJRNz54AuXKn8TJjhu0Wbv+wNEKQjVhnmKopjo4FxXmetCRnC4F7BhCiCUepqAepRh0TM/gjjzOOSK2NgWZPc05qampRWJHb7dbOffep2ednzLzgczlbrQA6gHYF9BYDh9GY+FjddMweHMscmMuep07gXlMQoqw9ALoYu5MJsak9QmJA2IvAgVmoCRciooyPujJtNCv1uHt3TmK9gegFKrG9kh6oXwZiIEAtBIjORGKNTWR/WeW8XVkbjuJepLAyloM8LmTN//njKZPbraATZaLjCHEww9Ei4FFiPg6Ja5gT6gxYgLgnRDHRQwJXbz2GOw0d4A3K4GXlUtMahJjYVxiYbrwOmxIS10bFnIBOSi6Tl9Jgs0zbOEX18wyEwgLPMrxD1Y4aCK8kmTpgYcpAF27Mzs42Hjx4kA8BICUlJfKArR7LcEvTB1xEC9AoEw9OPagWkVU/D1oesmK6U911zEczMVe01oZjiMggg6ux2Qk379qh4rYKet4GjrhhwEteBgBrH8BssoXEtbHzPpSBRRSpqlNpgAiUoxzHKxLRszoVuggIisxaDQWZqkQvQjAoax3NbDbLLGuUEABNGedXqSyLRupXgDT5JfAGZNLio9B0X8Uiwk4w77MDc1D4yejjWtykPS3DX01UDCY/GPQcVDe0QYT0CIxGFvUorfvBxZsRfVrUuWruMBAb/lXCUofoFNZfzGJtowXOX0vwUSFK4BgyMKm6P6s9wQUZld+jrYyMDC0iIQDaJdG4IyZQfL3RfbFcCBIlRgc+u3CjaTApuZ9KsANgG8PNzHlWWD3tCxd6kafNNiFp5HAalAkkJ0SCV2H3CgOD9Nc/FqrXuyb0Eocvfhq171p5eyuJ1omKJEP5rQGe/FOOnXtq335z8YmvYo9cHb2t8spIb3lVSseZW46FlGY/Sk9P50P2w20UlWJUkUHIushfc5PXGAzCo0PlD2pnpCYfCXga3lu+fPlevEhWrVrFyrN/Orfv87FOW9tlqb2Kc9pV8DzioMk3UNUbXM+8B/ATBr8C8CKdvGXWGD/9sqm3dkxtzA4McMjHMB8D2ftheYXo+qzt3pXvz8/PP/vk+v8537V+yYW87Zu+RZ1ZbrexoKAA/SBpaWn4+aL5w5zGk+/jW59JiMkESW5urpiVlWXENRb1H/Yf2I9txIxz5IdkX3TsraukpsbQjz6090yb4XsAvQoRE0YvJdamtIIbOnRoUVlZ2ftsLVQzIdEXHntsaZdimssVfCpFui109+BnWPsXaWLI/zactygAAAAASUVORK5CYII="); define("text!cockpit/ui/images/plus.png", "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAAZiS0dEANIA0gDS7KbF4AAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9kFGw4yFTwuJTkAAAH7SURBVCjPdZKxa1NRFMZ/956XZMgFyyMlCZRA4hBx6lBcQ00GoYi4tEstFPwLAs7iLDi7FWuHThaUggihBDI5OWRoQAmBQFISQgvvpbwX3rsOaR4K+o2H8zvfOZxPWWtZqVarGaAJPAEe3ZW/A1+Bd+1221v1qhW4vb1dA44mk0nZ8zyCIAAgk8lgjGF9fb0PHF5cXLQTsF6vP/c879P19TVBEJDJZBARAKIoSmpra2sYY561Wq3PqtFouMBgMBgYay3ZbJZ/yfd9tNaUSqUboOKISPPq6sqsVvZ9H4AvL34B8PTj/QSO45jpdHovn883Ha31znw+JwzDpCEMQx4UloM8zyOdTif3zudztNY7jog8DMMQpRRxHPPt5TCBAEZvxlyOFTsfykRRBICIlB2t9a21Nh3HMXEc8+d7VhJHWCwWyzcohdZaHBHpO46z6fs+IsLj94XECaD4unCHL8FsNouI/HRE5Nx13c3ZbIbWOnG5HKtl+53TSq7rIiLnand31wUGnU7HjEYjlFLJZN/3yRnL1FMYY8jlcmxtbd0AFel2u7dnZ2eXxpi9xWJBEASkUimstYgIQSSkUimKxSKVSgVjzN7p6emPJHL7+/s14KjX65WHwyGz2SxZbWNjg2q12gcOT05O2n9lFeDg4MAAr/4T8rfHx8dJyH8DvvbYGzKvWukAAAAASUVORK5CYII="); define("text!cockpit/ui/images/throbber.gif", "data:image/gif;base64,R0lGODlh3AATAPQAAP///wAAAL6+vqamppycnLi4uLKyssjIyNjY2MTExNTU1Nzc3ODg4OTk5LCwsLy8vOjo6Ozs7MrKyvLy8vT09M7Ozvb29sbGxtDQ0O7u7tbW1sLCwqqqqvj4+KCgoJaWliH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCgAAACwAAAAA3AATAAAF/yAgjmRpnmiqrmzrvnAsz3Rt33iu73zv/8CgcEgECAaEpHLJbDqf0Kh0Sq1ar9isdjoQtAQFg8PwKIMHnLF63N2438f0mv1I2O8buXjvaOPtaHx7fn96goR4hmuId4qDdX95c4+RG4GCBoyAjpmQhZN0YGYFXitdZBIVGAoKoq4CG6Qaswi1CBtkcG6ytrYJubq8vbfAcMK9v7q7D8O1ycrHvsW6zcTKsczNz8HZw9vG3cjTsMIYqQgDLAQGCQoLDA0QCwUHqfYSFw/xEPz88/X38Onr14+Bp4ADCco7eC8hQYMAEe57yNCew4IVBU7EGNDiRn8Z831cGLHhSIgdE/9chIeBgDoB7gjaWUWTlYAFE3LqzDCTlc9WOHfm7PkTqNCh54rePDqB6M+lR536hCpUqs2gVZM+xbrTqtGoWqdy1emValeXKwgcWABB5y1acFNZmEvXwoJ2cGfJrTv3bl69Ffj2xZt3L1+/fw3XRVw4sGDGcR0fJhxZsF3KtBTThZxZ8mLMgC3fRatCLYMIFCzwLEprg84OsDus/tvqdezZf13Hvr2B9Szdu2X3pg18N+68xXn7rh1c+PLksI/Dhe6cuO3ow3NfV92bdArTqC2Ebc3A8vjf5QWf15Bg7Nz17c2fj69+fnq+8N2Lty+fuP78/eV2X13neIcCeBRwxorbZrAxAJoCDHbgoG8RTshahQ9iSKEEzUmYIYfNWViUhheCGJyIP5E4oom7WWjgCeBBAJNv1DVV01MZdJhhjdkplWNzO/5oXI846njjVEIqR2OS2B1pE5PVscajkxhMycqLJgxQCwT40PjfAV4GqNSXYdZXJn5gSkmmmmJu1aZYb14V51do+pTOCmA00AqVB4hG5IJ9PvYnhIFOxmdqhpaI6GeHCtpooisuutmg+Eg62KOMKuqoTaXgicQWoIYq6qiklmoqFV0UoeqqrLbq6quwxirrrLTWauutJ4QAACH5BAkKAAAALAAAAADcABMAAAX/ICCOZGmeaKqubOu+cCzPdG3feK7vfO//wKBwSAQIBoSkcslsOp/QqHRKrVqv2Kx2OhC0BAXHx/EoCzboAcdhcLDdgwJ6nua03YZ8PMFPoBMca215eg98G36IgYNvDgOGh4lqjHd7fXOTjYV9nItvhJaIfYF4jXuIf4CCbHmOBZySdoOtj5eja59wBmYFXitdHhwSFRgKxhobBgUPAmdoyxoI0tPJaM5+u9PaCQZzZ9gP2tPcdM7L4tLVznPn6OQb18nh6NV0fu3i5OvP8/nd1qjwaasHcIPAcf/gBSyAAMMwBANYEAhWYQGDBhAyLihwYJiEjx8fYMxIcsGDAxVA/yYIOZIkBAaGPIK8INJlRpgrPeasaRPmx5QgJfB0abLjz50tSeIM+pFmUo0nQQIV+vRlTJUSnNq0KlXCSq09ozIFexEBAYkeNiwgOaEtn2LFpGEQsKCtXbcSjOmVlqDuhAx3+eg1Jo3u37sZBA9GoMAw4MB5FyMwfLht4sh7G/utPGHlYAV8Nz9OnOBz4c2VFWem/Pivar0aKCP2LFn2XwhnVxBwsPbuBAQbEGiIFg1BggoWkidva5z4cL7IlStfkED48OIYoiufYIH68+cKPkqfnsB58ePjmZd3Dj199/XE20tv6/27XO3S6z9nPCz9BP3FISDefL/Bt192/uWmAv8BFzAQAQUWWFaaBgqA11hbHWTIXWIVXifNhRlq6FqF1sm1QQYhdiAhbNEYc2KKK1pXnAIvhrjhBh0KxxiINlqQAY4UXjdcjSJyeAx2G2BYJJD7NZQkjCPKuCORKnbAIXsuKhlhBxEomAIBBzgIYXIfHfmhAAyMR2ZkHk62gJoWlNlhi33ZJZ2cQiKTJoG05Wjcm3xith9dcOK5X51tLRenoHTuud2iMnaolp3KGXrdBo7eKYF5p/mXgJcogClmcgzAR5gCKymXYqlCgmacdhp2UCqL96mq4nuDBTmgBasaCFp4sHaQHHUsGvNRiiGyep1exyIra2mS7dprrtA5++z/Z8ZKYGuGsy6GqgTIDvupRGE+6CO0x3xI5Y2mOTkBjD4ySeGU79o44mcaSEClhglgsKyJ9S5ZTGY0Bnzrj+3SiKK9Rh5zjAALCywZBk/ayCWO3hYM5Y8Dn6qxxRFsgAGoJwwgDQRtYXAAragyQOmaLKNZKGaEuUlpyiub+ad/KtPqpntypvvnzR30DBtjMhNodK6Eqrl0zU0/GjTUgG43wdN6Ra2pAhGtAAZGE5Ta8TH6wknd2IytNKaiZ+Or79oR/tcvthIcAPe7DGAs9Edwk6r3qWoTaNzY2fb9HuHh2S343Hs1VIHhYtOt+Hh551rh24vP5YvXSGzh+eeghy76GuikU9FFEainrvrqrLfu+uuwxy777LTXfkIIACH5BAkKAAAALAAAAADcABMAAAX/ICCOZGmeaKqubOu+cCzPdG3feK7vfO//wKBwSAQIBoSkcslsOp/QqHRKrVqv2Kx2OhC0BAWHB2l4CDZo9IDjcBja7UEhTV+3DXi3PJFA8xMcbHiDBgMPG31pgHBvg4Z9iYiBjYx7kWocb26OD398mI2EhoiegJlud4UFiZ5sm6Kdn2mBr5t7pJ9rlG0cHg5gXitdaxwFGArIGgoaGwYCZ3QFDwjU1AoIzdCQzdPV1c0bZ9vS3tUJBmjQaGXl1OB0feze1+faiBvk8wjnimn55e/o4OtWjp+4NPIKogsXjaA3g/fiGZBQAcEAFgQGOChgYEEDCCBBLihwQILJkxIe/3wMKfJBSQkJYJpUyRIkgwcVUJq8QLPmTYoyY6ZcyfJmTp08iYZc8MBkhZgxk9aEcPOlzp5FmwI9KdWn1qASurJkClRoWKwhq6IUqpJBAwQEMBYroAHkhLt3+RyzhgCDgAV48Wbgg+waAnoLMgTOm6DwQ8CLBzdGdvjw38V5JTg2lzhyTMeUEwBWHPgzZc4TSOM1bZia6LuqJxCmnOxv7NSsl1mGHHiw5tOuIWeAEHcFATwJME/ApgFBc3MVLEgPvE+Ddb4JokufPmFBAuvPXWu3MIF89wTOmxvOvp179evQtwf2nr6aApPyzVd3jn089e/8xdfeXe/xdZ9/d1ngHf98lbHH3V0LMrgPgsWpcFwBEFBgHmyNXWeYAgLc1UF5sG2wTHjIhNjBiIKZCN81GGyQwYq9uajeMiBOQGOLJ1KjTI40kmfBYNfc2NcGIpI4pI0vyrhjiT1WFqOOLEIZnjVOVpmajYfBiCSNLGbA5YdOkjdihSkQwIEEEWg4nQUmvYhYe+bFKaFodN5lp3rKvJYfnBKAJ+gGDMi3mmbwWYfng7IheuWihu5p32XcSWdSj+stkF95dp64jJ+RBipocHkCCp6PCiRQ6INookCAAwy0yd2CtNET3Yo7RvihBjFZAOaKDHT43DL4BQnsZMo8xx6uI1oQrHXXhHZrB28G62n/YSYxi+uzP2IrgbbHbiaer7hCiOxDFWhrbmGnLVuus5NFexhFuHLX6gkEECorlLpZo0CWJG4pLjIACykmBsp0eSSVeC15TDJeUhlkowlL+SWLNJpW2WEF87urXzNWSZ6JOEb7b8g1brZMjCg3ezBtWKKc4MvyEtwybPeaMAA1ECRoAQYHYLpbeYYCLfQ+mtL5c9CnfQpYpUtHOSejEgT9ogZ/GSqd0f2m+LR5WzOtHqlQX1pYwpC+WbXKqSYtpJ5Mt4a01lGzS3akF60AxkcTaLgAyRBPWCoDgHfJqwRuBuzdw/1ml3iCwTIeLUWJN0v4McMe7uasCTxseNWPSxc5RbvIgD7geZLbGrqCG3jepUmbbze63Y6fvjiOylbwOITPfIHEFsAHL/zwxBdvPBVdFKH88sw37/zz0Ecv/fTUV2/99SeEAAAh+QQJCgAAACwAAAAA3AATAAAF/yAgjmRpnmiqrmzrvnAsz3Rt33iu73zv/8CgcEgECAaEpHLJbDqf0Kh0Sq1ar9isdjoQtAQFh2cw8BQEm3T6yHEYHHD4oKCuD9qGvNsxT6QTgAkcHHmFeX11fm17hXwPG35qgnhxbwMPkXaLhgZ9gWp3bpyegX4DcG+inY+Qn6eclpiZkHh6epetgLSUcBxlD2csXXdvBQrHGgoaGhsGaIkFDwjTCArTzX+QadHU3c1ofpHc3dcGG89/4+TYktvS1NYI7OHu3fEJ5tpqBu/k+HX7+nXDB06SuoHm0KXhR65cQT8P3FRAMIAFgVMPwDCAwLHjggIHJIgceeFBg44eC/+ITCCBZYKSJ1FCWPBgpE2YMmc+qNCypwScMmnaXAkUJYOaFVyKLOqx5tCXJnMelcBzJNSYKIX2ZPkzqsyjPLku9Zr1QciVErYxaICAgEUOBRJIgzChbt0MLOPFwyBggV27eCUcmxZvg9+/dfPGo5bg8N/Ag61ZM4w4seDF1fpWhizZmoa+GSortgcaMWd/fkP/HY0MgWbTipVV++wY8GhvqSG4XUEgoYTKE+Qh0OCvggULiBckWEZ4Ggbjx5HXVc58IPQJ0idQJ66XanTpFraTe348+XLizRNcz658eHMN3rNPT+C+G/nodqk3t6a+fN3j+u0Xn3nVTQPfdRPspkL/b+dEIN8EeMm2GAYbTNABdrbJ1hyFFv5lQYTodSZABhc+loCEyhxTYYkZopdMMiNeiBxyIFajV4wYHpfBBspUl8yKHu6ooV5APsZjQxyyeNeJ3N1IYod38cgdPBUid6GCKfRWgAYU4IccSyHew8B3doGJHmMLkGkZcynKk2Z50Ym0zJzLbDCmfBbI6eIyCdyJmJmoqZmnBAXy9+Z/yOlZDZpwYihnj7IZpuYEevrYJ5mJEuqiof4l+NYDEXQpXQcMnNjZNDx1oGqJ4S2nF3EsqWrhqqVWl6JIslpAK5MaIqDeqjJq56qN1aTaQaPbHTPYr8Be6Gsyyh6Da7OkmmqP/7GyztdrNVQBm5+pgw3X7aoYKhfZosb6hyUKBHCgQKij1rghkOAJuZg1SeYIIY+nIpDvf/sqm4yNG5CY64f87qdAwSXKGqFkhPH1ZHb2EgYtw3bpKGVkPz5pJAav+gukjB1UHE/HLNJobWcSX8jiuicMMBFd2OmKwQFs2tjXpDfnPE1j30V3c7iRHlrzBD2HONzODyZtsQJMI4r0AUNaE3XNHQw95c9GC001MpIxDacFQ+ulTNTZlU3O1eWVHa6vb/pnQUUrgHHSBKIuwG+bCPyEqbAg25gMVV1iOB/IGh5YOKLKIQ6xBAcUHmzjIcIqgajZ+Ro42DcvXl7j0U4WOUd+2IGu7DWjI1pt4DYq8BPm0entuGSQY/4tBi9Ss0HqfwngBQtHbCH88MQXb/zxyFfRRRHMN+/889BHL/301Fdv/fXYZ39CCAAh+QQJCgAAACwAAAAA3AATAAAF/yAgjmRpnmiqrmzrvnAsz3Rt33iu73zv/8CgcEgECAaEpHLJbDqf0Kh0Sq1ar9isdjoQtAQFh2fAKXsKm7R6Q+Y43vABep0mGwwOPH7w2CT+gHZ3d3lyagl+CQNvg4yGh36LcHoGfHR/ZYOElQ9/a4ocmoRygIiRk5p8pYmZjXePaYBujHoOqp5qZHBlHAUFXitddg8PBg8KGsgayxvGkAkFDwgICtPTzX2mftHW3QnOpojG3dbYkNjk1waxsdDS1N7ga9zw1t/aifTk35fu6Qj3numL14fOuHTNECHqU4DDgQEsCCwidiHBAwYQMmpcUOCAhI8gJVzUuLGThAQnP/9abEAyI4MCIVOKZNnyJUqUJxNcGNlywYOQgHZirGkSJ8gHNEky+AkS58qWEJYC/bMzacmbQHkqNdlUJ1KoSz2i9COhmQYCEXtVrCBgwYS3cCf8qTcNQ9u4cFFOq2bPLV65Cf7dxZthbjW+CgbjnWtNgWPFcAsHdoxgWWK/iyV045sAc2S96SDn1exYw17REwpLQEYt2eW/qtPZRQAB7QoC61RW+GsBwYZ/CXb/XRCYLsAKFizEtUAc+G7lcZsjroscOvTmsoUvx15PwccJ0N8yL17N9PG/E7jv9S4hOV7pdIPDdZ+ePDzv2qMXn2b5+wTbKuAWnF3oZbABZY0lVmD/ApQd9thybxno2GGuCVDggaUpoyBsB1bGGgIYbJCBcuFJiOAyGohIInQSmmdeiBnMF2GHfNUlIoc1rncjYRjW6NgGf3VQGILWwNjBfxEZcAFbC7gHXQcfUYOYdwzQNxo5yUhQZXhvRYlMeVSuSOJHKJa5AQMQThBlZWZ6Bp4Fa1qzTAJbijcBlJrtxeaZ4lnnpZwpukWieGQmYx5ATXIplwTL8DdNZ07CtWYybNIJF4Ap4NZHe0920AEDk035kafieQrqXofK5ympn5JHKYjPrfoWcR8WWQGp4Ul32KPVgXdnqxM6OKqspjIYrGPDrlrsZtRIcOuR86nHFwbPvmes/6PH4frrqbvySh+mKGhaAARPzjjdhCramdoGGOhp44i+zogBkSDuWC5KlE4r4pHJkarXrj++Raq5iLmWLlxHBteavjG+6amJrUkJJI4Ro5sBv9AaOK+jAau77sbH7nspCwNIYIACffL7J4JtWQnen421nNzMcB6AqpRa9klonmBSiR4GNi+cJZpvwgX0ejj71W9yR+eIgaVvQgf0l/A8nWjUFhwtZYWC4hVnkZ3p/PJqNQ5NnwUQrQCGBBBMQIGTtL7abK+5JjAv1fi9bS0GLlJHgdjEgYzzARTwC1fgEWdJuKKBZzj331Y23qB3i9v5aY/rSUC4w7PaLeWXmr9NszMFoN79eeiM232o33EJAIzaSGwh++y012777bhT0UURvPfu++/ABy/88MQXb/zxyCd/QggAIfkECQoAAAAsAAAAANwAEwAABf8gII5kaZ5oqq5s675wLM90bd94ru987//AoHBIBAgGhKRyyWw6n9CodEqtWq/YrHY6ELQEBY5nwCk7xIWNer0hO95wziC9Ttg5b4ND/+Y87IBqZAaEe29zGwmJigmDfHoGiImTjXiQhJEPdYyWhXwDmpuVmHwOoHZqjI6kZ3+MqhyemJKAdo6Ge3OKbEd4ZRwFBV4rc4MPrgYPChrMzAgbyZSJBcoI1tfQoYsJydfe2amT3d7W0OGp1OTl0YtqyQrq0Lt11PDk3KGoG+nxBpvTD9QhwCctm0BzbOyMIwdOUwEDEgawIOCB2oMLgB4wgMCx44IHBySIHClBY0ePfyT/JCB5weRJCAwejFw58kGDlzBTqqTZcuPLmCIBiWx58+VHmiRLFj0JVCVLl0xl7qSZwCbOo0lFWv0pdefQrVFDJtr5gMBEYBgxqBWwYILbtxPsqMPAFu7blfa81bUbN4HAvXAzyLWnoDBguHIRFF6m4LBbwQngMYPXuC3fldbyPrMcGLM3w5wRS1iWWUNlvnElKDZtz/EEwaqvYahQoexEfyILi4RrYYKFZwJ3810QWZ2ECrx9Ew+O3K6F5Yq9zXbb+y30a7olJJ+wnLC16W97Py+uwdtx1NcLWzs/3G9e07stVPc9kHJ0BcLtQp+c3ewKAgYkUAFpCaAmmHqKLSYA/18WHEiZPRhsQF1nlLFWmIR8ZbDBYs0YZuCGpGXWmG92aWiPMwhEOOEEHXRwIALlwXjhio+BeE15IzpnInaLbZBBhhti9x2GbnVQo2Y9ZuCfCgBeMCB+DJDIolt4iVhOaNSJdCOBUfIlkmkyMpPAAvKJ59aXzTQzJo0WoJnmQF36Jp6W1qC4gWW9GZladCiyJd+KnsHImgRRVjfnaDEKuiZvbcYWo5htzefbl5LFWNeSKQAo1QXasdhiiwwUl2B21H3aQaghXnPcp1NagCqYslXAqnV+zYWcpNwVp9l5eepJnHqL4SdBi56CGlmw2Zn6aaiZjZqfb8Y2m+Cz1O0n3f+tnvrGbF6kToApCgAWoNWPeh754JA0vmajiAr4iOuOW7abQXVGNriBWoRdOK8FxNqLwX3oluubhv8yluRbegqGb536ykesuoXhyJqPQJIGbLvQhkcwjKs1zBvBwSZIsbcsDCCBAAf4ya+UEhyQoIiEJtfoZ7oxUOafE2BwgMWMqUydfC1LVtiArk0QtGkWEopzlqM9aJrKHfw5c6wKjFkmXDrbhwFockodtMGFLWpXy9JdiXN1ZDNszV4WSLQCGBKoQYHUyonqrHa4ErewAgMmcAAF7f2baIoVzC2p3gUvJtLcvIWqloy6/R04mIpLwDhciI8qLOB5yud44pHPLbA83hFDWPjNbuk9KnySN57Av+TMBvgEAgzzNhJb5K777rz37vvvVHRRxPDEF2/88cgnr/zyzDfv/PPQnxACACH5BAkKAAAALAAAAADcABMAAAX/ICCOZGmeaKqubOu+cCzPdG3feK7vfO//wKBwSAQIBoSkcslsOp/QqHRKrVqv2Kx2OhC0BIUCwcMpO84OT2HDbm8GHLQjnn6wE3g83SA3DB55G3llfHxnfnZ4gglvew6Gf4ySgmYGlpCJknochWiId3kJcZZyDn93i6KPl4eniopwq6SIoZKxhpenbhtHZRxhXisDopwPgHkGDxrLGgjLG8mC0gkFDwjX2AgJ0bXJ2djbgNJsAtbfCNB2oOnn6MmKbeXt226K1fMGi6j359D69ua+QZskjd+3cOvY9XNgp4ABCQNYEDBl7EIeCQkeMIDAseOCBwckiBSZ4ILGjh4B/40kaXIjSggMHmBcifHky5gYE6zM2OAlzGM6Z5rs+fIjTZ0tfcYMSlLCUJ8fL47kCVXmTjwPiKJkUCDnyqc3CxzQmYeAxAEGLGJYiwCDgAUT4sqdgOebArdw507IUNfuW71xdZ7DC5iuhGsKErf9CxhPYgUaEhPWyzfBMgUIJDPW6zhb5M1y+R5GjFkBaLmCM0dOfHqvztXYJnMejaFCBQlmVxAYsEGkYnQV4lqYMNyCtnYSggNekAC58uJxmTufW5w55mwKkg+nLp105uTC53a/nhg88fMTmDfDVl65Xum/IZt/3/zaag3a5W63nll1dvfiWbaaZLmpQIABCVQA2f9lAhTG112PQWYadXE9+FtmEwKWwQYQJrZagxomsOCAGVImInsSbpCBhhwug6KKcXXQQYUcYuDMggrASFmNzjjzzIrh7cUhhhHqONeGpSEW2QYxHsmjhxpgUGAKB16g4IIbMNCkXMlhaJ8GWVJo2I3NyKclYF1GxgyYDEAnXHJrMpNAm/rFBSczPiYAlwXF8ZnmesvoOdyMbx7m4o0S5LWdn4bex2Z4xYmEzaEb5EUcnxbA+WWglqIn6aHPTInCgVbdlZyMqMrIQHMRSiaBBakS1903p04w434n0loBoQFOt1yu2YAnY68RXiNsqh2s2qqxuyKb7Imtmgcrqsp6h8D/fMSpapldx55nwayK/SfqCQd2hcFdAgDp5GMvqhvakF4mZuS710WGIYy30khekRkMu92GNu6bo7r/ttjqwLaua5+HOdrKq5Cl3dcwi+xKiLBwwwom4b0E6xvuYyqOa8IAEghwQAV45VvovpkxBl2mo0W7AKbCZXoAhgMmWnOkEqx2JX5nUufbgJHpXCfMOGu2QAd8eitpW1eaNrNeMGN27mNz0swziYnpSbXN19gYtstzfXrdYjNHtAIYGFVwwAEvR1dfxdjKxVzAP0twAAW/ir2w3nzTd3W4yQWO3t0DfleB4XYnEHCEhffdKgaA29p0eo4fHLng9qoG+OVyXz0gMeWGY7qq3xhiRIEAwayNxBawxy777LTXbjsVXRSh++689+7778AHL/zwxBdv/PEnhAAAIfkECQoAAAAsAAAAANwAEwAABf8gII5kaZ5oqq5s675wLM90bd94ru987//AoHBIBAgGhKRyyWw6n9CodEqtWq/YrHY6ELQEhYLD4BlwHGg0ubBpuzdm9Dk9eCTu+MTZkDb4PXYbeIIcHHxqf4F3gnqGY2kOdQmCjHCGfpCSjHhmh2N+knmEkJmKg3uHfgaaeY2qn6t2i4t7sKAPbwIJD2VhXisDCQZgDrKDBQ8aGgjKyhvDlJMJyAjV1gjCunkP1NfVwpRtk93e2ZVt5NfCk27jD97f0LPP7/Dr4pTp1veLgvrx7AL+Q/BM25uBegoYkDCABYFhEobhkUBRwoMGEDJqXPDgQMUEFC9c1LjxQUUJICX/iMRIEgIDkycrjmzJMSXFlDNJvkwJsmdOjQwKfDz5M+PLoSGLQqgZU6XSoB/voHxawGbFlS2XGktAwKEADB0xiEWAodqGBRPSqp1wx5qCamDRrp2Qoa3bagLkzrULF4GCvHPTglRAmKxZvWsHayBcliDitHUlvGWM97FgCdYWVw4c2e/kw4HZJlCwmDBhwHPrjraGYTHqtaoxVKggoesKAgd2SX5rbUMFCxOAC8cGDwHFwBYWJCgu4XfwtcqZV0grPHj0u2SnqwU+IXph3rK5b1fOu7Bx5+K7L6/2/Xhg8uyXnQ8dvfRiDe7TwyfNuzlybKYpgIFtKhAgwEKkKcOf/wChZbBBgMucRh1so5XH3wbI1WXafRJy9iCErmX4IWHNaIAhZ6uxBxeGHXQA24P3yYfBBhmgSBozESpwongWOBhggn/N1aKG8a1YY2oVAklgCgQUUwGJ8iXAgItrWUARbwpqIOWEal0ZoYJbzmWlZCWSlsAC6VkwZonNbMAAl5cpg+NiZwpnJ0Xylegmlc+tWY1mjnGnZnB4QukMA9UJRxGOf5r4ppqDjjmnfKilh2ejGiyJAgF1XNmYbC2GmhZ5AcJVgajcXecNqM9Rx8B6bingnlotviqdkB3YCg+rtOaapFsUhSrsq6axJ6sEwoZK7I/HWpCsr57FBxJ1w8LqV/81zbkoXK3LfVeNpic0KRQG4NHoIW/XEmZuaiN6tti62/moWbk18uhjqerWS6GFpe2YVotskVssWfBOAHACrZHoWcGQwQhlvmsdXBZ/F9YLMF2jzUuYBP4a7CLCnoEHrgkDSCDAARUILAGaVVqAwQHR8pZXomm9/ONhgjrbgc2lyYxmpIRK9uSNjrXs8gEbTrYyl2ryTJmsLCdKkWzFQl1lWlOXGmifal6p9VnbQfpyY2SZyXKVV7JmZkMrgIFSyrIeUJ2r7YKnXdivUg1kAgdQ8B7IzJjGsd9zKSdwyBL03WpwDGxwuOASEP5vriO2F3nLjQdIrpaRDxqcBdgIHGA74pKrZXiR2ZWuZt49m+o3pKMC3p4Av7SNxBa456777rz37jsVXRQh/PDEF2/88cgnr/zyzDfv/PMnhAAAIfkECQoAAAAsAAAAANwAEwAABf8gII5kaZ5oqq5s675wLM90bd94ru987//AoHBIBAgGhKRyyWw6n9CodEqtWq/YrHY6ELQEhYLDUPAMHGi0weEpbN7wI8cxTzsGj4R+n+DUxwaBeBt7hH1/gYIPhox+Y3Z3iwmGk36BkIN8egOIl3h8hBuOkAaZhQlna4BrpnyWa4mleZOFjrGKcXoFA2ReKwMJBgISDw6abwUPGggazc0bBqG0G8kI1tcIwZp51djW2nC03d7BjG8J49jl4cgP3t/RetLp1+vT6O7v5fKhAvnk0UKFogeP3zmCCIoZkDCABQFhChQYuKBHgkUJkxpA2MhxQYEDFhNcvPBAI8eNCx7/gMQYckPJkxsZPLhIM8FLmDJrYiRp8mTKkCwT8IQJwSPQkENhpgQpEunNkzlpWkwKdSbGihKocowqVSvKWQkIOBSgQOYFDBgQpI0oYMGEt3AzTLKm4BqGtnDjirxW95vbvG/nWlub8G9euRsiqqWLF/AEkRoiprX2wLDeDQgkW9PQGLDgyNc665WguK8C0XAnRY6oGPUEuRLsgk5g+a3cCxUqSBC7gsCBBXcVq6swwULx4hayvctGPK8FCwsSLE9A3Hje6NOrHzeOnW695sffRi/9HfDz7sIVSNB+XXrmugo0rHcM3X388o6jr44ceb51uNjF1xcC8zk3wXiS8aYC/wESaLABBs7ch0ECjr2WAGvLsLZBeHqVFl9kGxooV0T81TVhBo6NiOEyJ4p4IYnNRBQiYCN6x4wCG3ZAY2If8jXjYRcyk2FmG/5nXAY8wqhWAii+1YGOSGLoY4VRfqiAgikwmIeS1gjAgHkWYLQZf9m49V9gDWYWY5nmTYCRM2TS5pxxb8IZGV5nhplmhJyZadxzbrpnZ2d/6rnZgHIid5xIMDaDgJfbLdrgMkKW+Rygz1kEZz1mehabkBpgiQIByVikwGTqVfDkk2/Vxxqiqur4X3fksHccre8xlxerDLiHjQIVUAgXr77yFeyuOvYqXGbMrbrqBMqaFpFFzhL7qv9i1FX7ZLR0LUNdcc4e6Cus263KbV+inkAAHhJg0BeITR6WmHcaxhvXg/AJiKO9R77ILF1FwmVdAu6WBu+ZFua72mkZWMfqBElKu0G8rFZ5n4ATp5jkmvsOq+Nj7u63ZMMPv4bveyYy6fDH+C6brgnACHBABQUrkGirz2FwAHnM4Mmhzq9yijOrOi/MKabH6VwBiYwZdukEQAvILKTWXVq0ZvH5/CfUM7M29Zetthp1eht0eqkFYw8IKXKA6mzXfTeH7fZg9zW0AhgY0TwthUa6Ch9dBeIsbsFrYkRBfgTfiG0FhwMWnbsoq3cABUYOnu/ejU/A6uNeT8u4wMb1WnBCyJJTLjjnr8o3OeJrUcpc5oCiPqAEkz8tXuLkPeDL3Uhs4fvvwAcv/PDEU9FFEcgnr/zyzDfv/PPQRy/99NRXf0IIACH5BAkKAAAALAAAAADcABMAAAX/ICCOZGmeaKqubOu+cCzPdG3feK7vfO//wKBwSAQIBoSkcslsOp/QqHRKrVqv2Kx2OhC0BIWCw/AoDziOtCHt8BQ28PjmzK57Hom8fo42+P8DeAkbeYQcfX9+gYOFg4d1bIGEjQmPbICClI9/YwaLjHAJdJeKmZOViGtpn3qOqZineoeJgG8CeWUbBV4rAwkGAhIVGL97hGACGsrKCAgbBoTRhLvN1c3PepnU1s2/oZO6AtzdBoPf4eMI3tIJyOnF0YwFD+nY8e3z7+Xfefnj9uz8cVsXCh89axgk7BrAggAwBQsYIChwQILFixIeNIDAseOCBwcSXMy2sSPHjxJE/6a0eEGjSY4MQGK86PIlypUJEmYsaTKmyJ8JW/Ls6HMkzaEn8YwMWtPkx4pGd76E4DMPRqFTY860OGhogwYagBFoKEABA46DEGBAoEBB0AUT4sqdIFKBNbcC4M6dkEEk22oYFOTdG9fvWrtsBxM23MytYL17666t9phwXwlum2lIDHmuSA2IGyuOLOHv38qLMbdFjHruZbWgRXeOe1nC2BUEDiyAMMHZuwoTLAQX3nvDOAUW5Vogru434d4JnAsnPmFB9NBshQXfa9104+Rxl8e13rZxN+CEydtVsFkd+vDjE7C/q52wOvb4s7+faz025frbxefWbSoQIAEDEUCwgf9j7bUlwHN9ZVaegxDK1xYzFMJH24L5saXABhlYxiEzHoKoIV8LYqAMaw9aZqFmJUK4YHuNfRjiXhmk+NcyJgaIolvM8BhiBx3IleN8lH1IWAcRgkZgCgYiaBGJojGgHHFTgtagAFYSZhF7/qnTpY+faVlNAnqJN0EHWa6ozAZjBtgmmBokwMB01LW5jAZwbqfmlNips4B4eOqJgDJ2+imXRZpthuigeC6XZTWIxilXmRo8iYKBCwiWmWkJVEAkfB0w8KI1IvlIpKnOkVpqdB5+h96o8d3lFnijrgprjbfGRSt0lH0nAZG5vsprWxYRW6Suq4UWqrLEsspWg8Io6yv/q6EhK0Fw0GLbjKYn5CZYBYht1laPrnEY67kyrhYbuyceiR28Pso7bYwiXjihjWsWuWF5p/H765HmNoiur3RJsGKNG/jq748XMrwmjhwCfO6QD9v7LQsDxPTAMKsFpthyJCdkmgYiw0VdXF/Om9dyv7YMWGXTLYpZg5wNR11C78oW3p8HSGgul4qyrJppgllJHJZHn0Y0yUwDXCXUNquFZNLKyYXBAVZvxtAKYIQEsmPgDacr0tltO1y/DMwYpkgUpJfTasLGzd3cdCN3gN3UWRcY3epIEPevfq+3njBxq/kqBoGBduvea8f393zICS63ivRBTqgFpgaWZEIUULdcK+frIfAAL2AjscXqrLfu+uuwx05FF0XUbvvtuOeu++689+7778AHL/wJIQAAOwAAAAAAAAAAAA=="); ================================================ FILE: dirigible/shared/static/ace/cockpit.js ================================================ define("cockpit/index",function(a,b,c){b.startup=function(b,c){a("pilot/index"),a("cockpit/cli").startup(b,c),a("cockpit/ui/settings").startup(b,c),a("cockpit/ui/cli_view").startup(b,c),a("cockpit/commands/basic").startup(b,c)}}),define("cockpit/cli",function(a,b,c){function r(a,b){q.call(this,a),b&&b.flags&&(this.flags=b.flags)}function q(a){this.env=a,this.commandAssignment=new o(p,this)}function o(a,b){this.param=a,this.requisition=b,this.setValue(a.defaultValue)}function n(a,b,c,d,e,f){this.emitter=a,this.setText(b),this.start=c,this.end=d,this.prefix=e,this.suffix=f}function m(a,b){this.status=a.status,this.message=a.message,b?(this.start=b.start,this.end=b.end):(this.start=0,this.end=0),this.predictions=a.predictions}function l(a,b,c,d,e){this.status=a,this.message=b;if(typeof c==="number")this.start=c,this.end=d,this.predictions=e;else{var f=c;this.start=f.start,this.end=f.end,this.predictions=f.predictions}}var d=a("pilot/console"),e=a("pilot/lang"),f=a("pilot/oop"),g=a("pilot/event_emitter").EventEmitter,h=a("pilot/types"),i=a("pilot/types").Status,j=a("pilot/types").Conversion,k=a("pilot/canon");b.startup=function(a,b){k.upgradeType("command",p)},l.prototype={},l.sort=function(a,b){b!==undefined&&a.forEach(function(a){a.start===n.AT_CURSOR?a.distance=0:ba.end?a.distance=b-a.end:a.distance=0},this),a.sort(function(a,c){if(b!==undefined){var d=a.distance-c.distance;if(d!=0)return d}return c.status-a.status}),b!==undefined&&a.forEach(function(a){delete a.distance},this);return a},b.Hint=l,f.inherits(m,l),n.prototype={merge:function(a){if(a.emitter!=this.emitter)throw new Error("Can't merge Arguments from different EventEmitters");return new n(this.emitter,this.text+this.suffix+a.prefix+a.text,this.start,a.end,this.prefix,a.suffix)},setText:function(a){if(a==null)throw new Error("Illegal text for Argument: "+a);var b={argument:this,oldText:this.text,text:a};this.text=a,this.emitter._dispatchEvent("argumentChange",b)},toString:function(){return this.prefix+this.text+this.suffix}},n.merge=function(a,b,c){b=b===undefined?0:b,c=c===undefined?a.length:c;var d;for(var e=b;e: ";this.param.description&&(b+=this.param.description.trim(),b.charAt(b.length-1)!=="."&&(b+="."),b.charAt(b.length-1)!==" "&&(b+=" "));var c=i.VALID,d=this.arg?this.arg.start:n.AT_CURSOR,e=this.arg?this.arg.end:n.AT_CURSOR,f;this.conversion&&(c=this.conversion.status,this.conversion.message&&(b+=this.conversion.message),f=this.conversion.predictions);var g=this.arg&&this.arg.text!=="",h=this.value!==undefined||g;this.param.defaultValue===undefined&&!h&&(c=i.INVALID,b+="Required");return new l(c,b,d,e,f)},complete:function(){this.conversion&&this.conversion.predictions&&this.conversion.predictions.length>0&&this.setValue(this.conversion.predictions[0])},isPositionCaptured:function(a){if(!this.arg)return!1;if(this.arg.start===-1)return!1;if(a>this.arg.end)return!1;if(a===this.arg.end)return this.conversion.status!==i.VALID||this.conversion.predictions.length!==0;return!0},decrement:function(){var a=this.param.type.decrement(this.value);a!=null&&this.setValue(a)},increment:function(){var a=this.param.type.increment(this.value);a!=null&&this.setValue(a)},toString:function(){return this.arg?this.arg.toString():""}},b.Assignment=o;var p={name:"__command",type:"command",description:"The command to execute",getCustomHint:function(a,b){var c=[];c.push(" > "),c.push(a.name),a.params&&a.params.length>0&&a.params.forEach(function(a){a.defaultValue===undefined?c.push(" ["+a.name+"]"):c.push(" ["+a.name+"]")},this),c.push("
    "),c.push(a.description?a.description:"(No description)"),c.push("
    "),a.params&&a.params.length>0&&(c.push("
      "),a.params.forEach(function(a){c.push("
    • "),c.push(""+a.name+": "),c.push(a.description?a.description:"(No description)"),a.defaultValue===undefined?c.push(" [Required]"):a.defaultValue===null?c.push(" [Optional]"):c.push(" [Default: "+a.defaultValue+"]"),c.push("
    • ")},this),c.push("
    "));return new l(i.VALID,c.join(""),b)}};q.prototype={commandAssignment:undefined,assignmentCount:undefined,_assignments:undefined,_hints:undefined,_assignmentChanged:function(a){a.param.name==="__command"&&(this._assignments={},a.value&&a.value.params.forEach(function(a){this._assignments[a.name]=new o(a,this)},this),this.assignmentCount=Object.keys(this._assignments).length,this._dispatchEvent("commandChange",{command:a.value}))},getAssignment:function(a){var b=typeof a==="string"?a:Object.keys(this._assignments)[a];return this._assignments[b]},getParameterNames:function(){return Object.keys(this._assignments)},cloneAssignments:function(){return Object.keys(this._assignments).map(function(a){return this._assignments[a]},this)},_updateHints:function(){this.getAssignments(!0).forEach(function(a){this._hints.push(a.getHint())},this),l.sort(this._hints)},getWorstHint:function(){return this._hints[0]},getArgsObject:function(){var a={};this.getAssignments().forEach(function(b){a[b.param.name]=b.value},this);return a},getAssignments:function(a){var b=[];a===!0&&b.push(this.commandAssignment),Object.keys(this._assignments).forEach(function(a){b.push(this.getAssignment(a))},this);return b},setDefaultValues:function(){this.getAssignments().forEach(function(a){a.setValue(undefined)},this)},exec:function(){k.exec(this.commandAssignment.value,this.env,"cli",this.getArgsObject(),this.toCanonicalString())},toCanonicalString:function(){var a=[];a.push(this.commandAssignment.value.name),Object.keys(this._assignments).forEach(function(b){var c=this._assignments[b],d=c.param.type;c.value!==c.param.defaultValue&&(a.push(" "),a.push(d.stringify(c.value)))},this);return a.join("")}},f.implement(q.prototype,g),b.Requisition=q,f.inherits(r,q),function(){r.prototype.update=function(a){this.input=a,this._hints=[];var b=this._tokenize(a.typed);this._split(b),this.commandAssignment.value&&this._assign(b),this._updateHints()},r.prototype.getInputStatusMarkup=function(){var a=this.toString().split("").map(function(a){return i.VALID});this._hints.forEach(function(b){for(var c=b.start;c<=b.end;c++)b.status>a[c]&&(a[c]=b.status)},this);return a},r.prototype.toString=function(){return this.getAssignments(!0).map(function(a){return a.toString()},this).join("")};var a=r.prototype._updateHints;r.prototype._updateHints=function(){a.call(this);var b=this.input.cursor;this._hints.forEach(function(a){var c=b.start>=a.start&&b.start<=a.end,d=b.end>=a.start&&b.end<=a.end,e=c||d;!e&&a.status===i.INCOMPLETE&&(a.status=i.INVALID)},this),l.sort(this._hints)},r.prototype.getHints=function(){return this._hints},r.prototype.getAssignmentAt=function(a){var b=this.getAssignments(!0);for(var c=0;c=a.length){if(f!==b){var l=g(a.substring(i,h));k.push(new n(this,l,i,h,j,""))}else if(h!==i){var m=a.substring(i,h),o=k[k.length-1];o?o.suffix+=m:(o=new n(this,"",h,h,m,""),k.push(o))}break}var p=a[h];switch(f){case b:p==="'"?(j=a.substring(i,h+1),f=d,i=h+1):p==='"'?(j=a.substring(i,h+1),f=e,i=h+1):/ /.test(p)||(j=a.substring(i,h),f=c,i=h);break;case c:if(p===" "){var l=g(a.substring(i,h));k.push(new n(this,l,i,h,j,"")),f=b,i=h,j=""}break;case d:if(p==="'"){var l=g(a.substring(i,h));k.push(new n(this,l,i-1,h+1,j,p)),f=b,i=h+1,j=""}break;case e:if(p==='"'){var l=g(a.substring(i,h));k.push(new n(this,l,i-1,h+1,j,p)),f=b,i=h+1,j=""}}h++}return k},r.prototype._split=function(a){var b=1,c;while(b<=a.length){var c=n.merge(a,0,b);this.commandAssignment.setArgument(c);if(!this.commandAssignment.value)break;if(this.commandAssignment.value.exec){for(var d=0;d=a.length)break;continue}b.param.type.name==="boolean"?b.setValue(!0):f+10){var g=n.merge(a);this._hints.push(new l(i.INVALID,"Input '"+g.text+"' makes no sense.",g))}}}}(),b.CliRequisition=r}),define("cockpit/ui/settings",function(a,b,c){var d=a("pilot/types"),e=a("pilot/types/basic").SelectionType,f=new e({name:"direction",data:["above","below"]}),g={name:"hintDirection",description:"Are hints shown above or below the command line?",type:"direction",defaultValue:"above"},h={name:"outputDirection",description:"Is the output window shown above or below the command line?",type:"direction",defaultValue:"above"},i={name:"outputHeight",description:"What height should the output panel be?",type:"number",defaultValue:300};b.startup=function(a,b){d.registerType(f),a.env.settings.addSetting(g),a.env.settings.addSetting(h),a.env.settings.addSetting(i)},b.shutdown=function(a,b){d.unregisterType(f),a.env.settings.removeSetting(g),a.env.settings.removeSetting(h),a.env.settings.removeSetting(i)}}),define("cockpit/ui/cli_view",function(a,b,c){function n(a,b){a.cliView=this,this.cli=a,this.doc=document,this.win=f.getParentWindow(this.doc),this.env=b,this.element=this.doc.getElementById("cockpitInput");!this.element||(this.settings=b.settings,this.hintDirection=this.settings.getSetting("hintDirection"),this.outputDirection=this.settings.getSetting("outputDirection"),this.outputHeight=this.settings.getSetting("outputHeight"),this.isUpdating=!1,this.createElements(),this.update())}var d=a("text!cockpit/ui/cli_view.css"),e=a("pilot/event"),f=a("pilot/dom");f.importCssString(d);var e=a("pilot/event"),g=a("pilot/keys"),h=a("pilot/canon"),i=a("pilot/types").Status,j=a("cockpit/cli").CliRequisition,k=a("cockpit/cli").Hint,l=a("cockpit/ui/request_view").RequestView,m=new k(i.VALID,"",0,0);b.startup=function(a,b){var c=new j(a.env),d=new n(c,a.env);a.env.cli=c},n.prototype={createElements:function(){function d(){f.removeCssClass(this.output,"cptFocusPopup"),f.removeCssClass(this.hinter,"cptFocusPopup")}var a=this.element;this.element.spellcheck=!1,this.output=this.doc.getElementById("cockpitOutput"),this.popupOutput=this.output==null;if(!this.output){this.output=this.doc.createElement("div"),this.output.id="cockpitOutput",this.output.className="cptOutput",a.parentNode.insertBefore(this.output,a.nextSibling);var b=function(){this.output.style.maxHeight=this.outputHeight.get()+"px"}.bind(this);this.outputHeight.addEventListener("change",b),b()}this.completer=this.doc.createElement("div"),this.completer.className="cptCompletion VALID",this.completer.style.color=f.computedStyle(a,"color"),this.completer.style.fontSize=f.computedStyle(a,"fontSize"),this.completer.style.fontFamily=f.computedStyle(a,"fontFamily"),this.completer.style.fontWeight=f.computedStyle(a,"fontWeight"),this.completer.style.fontStyle=f.computedStyle(a,"fontStyle"),a.parentNode.insertBefore(this.completer,a.nextSibling),this.completer.style.backgroundColor=a.style.backgroundColor,a.style.backgroundColor="transparent",this.hinter=this.doc.createElement("div"),this.hinter.className="cptHints",a.parentNode.insertBefore(this.hinter,a.nextSibling);var c=this.resizer.bind(this);e.addListener(this.win,"resize",c),this.hintDirection.addEventListener("change",c),this.outputDirection.addEventListener("change",c),c(),h.addEventListener("output",function(a){new l(a.request,this)}.bind(this)),e.addCommandKeyListener(a,this.onCommandKey.bind(this)),e.addListener(a,"keyup",this.onKeyUp.bind(this)),e.addListener(a,"mouseup",function(a){this.isUpdating=!0,this.update(),this.isUpdating=!1}.bind(this)),this.cli.addEventListener("argumentChange",this.onArgChange.bind(this)),e.addListener(a,"focus",function(){f.addCssClass(this.output,"cptFocusPopup"),f.addCssClass(this.hinter,"cptFocusPopup")}.bind(this)),e.addListener(a,"blur",d.bind(this)),d.call(this)},scrollOutputToBottom:function(){var a=Math.max(this.output.scrollHeight,this.output.clientHeight);this.output.scrollTop=a-this.output.clientHeight},resizer:function(){var a=this.element.getClientRects()[0];this.completer.style.top=a.top+"px";var b=a.bottom-a.top;this.completer.style.height=b+"px",this.completer.style.lineHeight=b+"px",this.completer.style.left=a.left+"px";var c=a.right-a.left;this.completer.style.width=c+"px",this.hintDirection.get()==="below"?(this.hinter.style.top=a.bottom+"px",this.hinter.style.bottom="auto"):(this.hinter.style.top="auto",this.hinter.style.bottom=this.doc.documentElement.clientHeight-a.top+"px"),this.hinter.style.left=a.left+30+"px",this.hinter.style.maxWidth=c-110+"px",this.popupOutput&&(this.outputDirection.get()==="below"?(this.output.style.top=a.bottom+"px",this.output.style.bottom="auto"):(this.output.style.top="auto",this.output.style.bottom=this.doc.documentElement.clientHeight-a.top+"px"),this.output.style.left=a.left+"px",this.output.style.width=c-80+"px")},onCommandKey:function(a,b,c){var d;if(c===g.TAB||c===g.UP||c===g.DOWN)d=!0;else if(b!=0||c!=0)d=h.execKeyCommand(this.env,"cli",b,c);d&&e.stopEvent(a)},onKeyUp:function(a){var b;if(a.keyCode===g.RETURN){var c=this.cli.getWorstHint();c.status===i.VALID?(this.cli.exec(),this.element.value=""):(f.setSelectionStart(this.element,c.start),f.setSelectionEnd(this.element,c.end))}this.update();var d=this.cli.getAssignmentAt(f.getSelectionStart(this.element));d&&(a.keyCode===g.TAB&&(d.complete(),this.update()),a.keyCode===g.UP&&(d.increment(),this.update()),a.keyCode===g.DOWN&&(d.decrement(),this.update()));return b},update:function(){this.isUpdating=!0;var a={typed:this.element.value,cursor:{start:f.getSelectionStart(this.element),end:f.getSelectionEnd(this.element.selectionEnd)}};this.cli.update(a);var b=this.cli.getAssignmentAt(a.cursor.start).getHint();f.removeCssClass(this.completer,i.VALID.toString()),f.removeCssClass(this.completer,i.INCOMPLETE.toString()),f.removeCssClass(this.completer,i.INVALID.toString());var c='> ';if(this.element.value.length>0){var d=this.cli.getInputStatusMarkup();c+=this.markupStatusScore(d)}if(this.element.value.length>0&&b.predictions&&b.predictions.length>0){var e=b.predictions[0];c+="  ⇥ "+(e.name?e.name:e)}this.completer.innerHTML=c,f.addCssClass(this.completer,this.cli.getWorstHint().status.toString());var g="";this.element.value.length!==0&&(g+=b.message,b.predictions&&b.predictions.length>0&&(g+=": [ ",b.predictions.forEach(function(a){g+=a.name?a.name:a,g+=" | "},this),g=g.replace(/\| $/,"]"))),this.hinter.innerHTML=g,g.length===0?f.addCssClass(this.hinter,"cptNoPopup"):f.removeCssClass(this.hinter,"cptNoPopup"),this.isUpdating=!1},markupStatusScore:function(a){var b="",c=0,d=-1;while(!0){d!==a[c]&&(b+="",d=a[c]),b+=this.element.value[c],c++;if(c===this.element.value.length){b+="";break}d!==a[c]&&(b+="")}return b},onArgChange:function(a){if(!this.isUpdating){var b=this.element.value.substring(0,a.argument.start),c=this.element.value.substring(a.argument.end),d=typeof a.text==="string"?a.text:a.text.name;this.element.value=b+d+c;var e=(b+d).length;this.element.selectionStart=e,this.element.selectionEnd=e}}},b.CliView=n}),define("cockpit/ui/request_view",function(a,b,c){function l(a,b){this.request=a,this.cliView=b,this.imageUrl=k,this.rowin=null,this.rowout=null,this.output=null,this.hide=null,this.show=null,this.duration=null,this.throb=null,(new g).processNode(j.cloneNode(!0),this),this.cliView.output.appendChild(this.rowin),this.cliView.output.appendChild(this.rowout),this.request.addEventListener("output",this.onRequestChange.bind(this))}function k(b){var d;try{d=a("text!cockpit/ui/"+b)}catch(e){}if(d)return d;var f=c.id.split("/").pop()+".js",g;if(c.uri.substr(-f.length)!==f){console.error("Can't work out path from module.uri/module.id");return b}if(c.uri){var h=c.uri.length-f.length-1;return c.uri.substr(0,h)+"/"+b}return f+b}var d=a("pilot/dom"),e=a("pilot/event"),f=a("text!cockpit/ui/request_view.html"),g=a("pilot/domtemplate").Templater,h=a("text!cockpit/ui/request_view.css");d.importCssString(h);var i=document.createElement("div");i.innerHTML=f;var j=i.querySelector(".cptRow");l.prototype={copyToInput:function(){this.cliView.element.value=this.request.typed},executeRequest:function(a){this.cliView.cli.update({typed:this.request.typed,cursor:{start:0,end:0}}),this.cliView.cli.exec()},hideOutput:function(a){this.output.style.display="none",d.addCssClass(this.hide,"cmd_hidden"),d.removeCssClass(this.show,"cmd_hidden"),e.stopPropagation(a)},showOutput:function(a){this.output.style.display="block",d.removeCssClass(this.hide,"cmd_hidden"),d.addCssClass(this.show,"cmd_hidden"),e.stopPropagation(a)},remove:function(a){this.cliView.output.removeChild(this.rowin),this.cliView.output.removeChild(this.rowout),e.stopPropagation(a)},onRequestChange:function(a){this.duration.innerHTML=this.request.duration?"completed in "+this.request.duration/1e3+" sec ":"",this.output.innerHTML="",this.request.outputs.forEach(function(a){var b;typeof a=="string"?(b=document.createElement("p"),b.innerHTML=a):b=a,this.output.appendChild(b)},this),this.cliView.scrollOutputToBottom(),d.setCssClass(this.output,"cmd_error",this.request.error),this.throb.style.display=this.request.completed?"none":"block"}},b.RequestView=l}),define("pilot/domtemplate",function(require,exports,module){function Templater(){this.scope=[]}Templater.prototype.processNode=function(a,b){typeof a==="string"&&(a=document.getElementById(a));if(b===null||b===undefined)b={};this.scope.push(a.nodeName+(a.id?"#"+a.id:""));try{if(a.attributes&&a.attributes.length){if(a.hasAttribute("foreach")){this.processForEach(a,b);return}if(a.hasAttribute("if")&&!this.processIf(a,b))return;b.__element=a;var c=Array.prototype.slice.call(a.attributes);for(var d=0;d1&&(d.forEach(function(c){c!==null&&c!==undefined&&c!==""&&(c.charAt(0)==="$"&&(c=this.envEval(c.slice(1),b,a.data)),c===null&&(c="null"),c===undefined&&(c="undefined"),typeof c.cloneNode!=="function"&&(c=a.ownerDocument.createTextNode(c.toString())),a.parentNode.insertBefore(c,a))},this),a.parentNode.removeChild(a))},Templater.prototype.stripBraces=function(a){if(!a.match(/\$\{.*\}/g)){this.handleError("Expected "+a+" to match ${...}");return a}return a.slice(2,-1)},Templater.prototype.property=function(a,b,c){this.scope.push(a);try{typeof a==="string"&&(a=a.split("."));var d=b[a[0]];if(a.length===1){c!==undefined&&(b[a[0]]=c);if(typeof d==="function")return function(){return d.apply(b,arguments)};return d}if(!d){this.handleError("Can't find path="+a);return null}return this.property(a.slice(1),d,c)}finally{this.scope.pop()}},Templater.prototype.envEval=function(script,env,context){with(env)try{this.scope.push(context);return eval(script)}catch(ex){this.handleError("Template error evaluating '"+script+"'",ex);return script}finally{this.scope.pop()}},Templater.prototype.handleError=function(a,b){this.logError(a),this.logError("In: "+this.scope.join(" > ")),b&&this.logError(b)},Templater.prototype.logError=function(a){window.console&&window.console.log&&console.log(a)},exports.Templater=Templater}),define("cockpit/commands/basic",function(a,b,c){var d=a("pilot/canon"),e={name:"sh",description:"Execute a system command (requires server support)",params:[{name:"command",type:"text",description:"The string to send to the os shell."}],exec:function(a,b,c){var d=new XMLHttpRequest;d.open("GET","/exec?args="+b.command,!0),d.onreadystatechange=function(a){d.readyState==4&&d.status==200&&c.done("
    "+d.responseText+"
    ")},d.send(null)}},d=a("pilot/canon");b.startup=function(a,b){d.addCommand(e)},b.shutdown=function(a,b){d.removeCommand(e)}}),define("text!cockpit/ui/cli_view.css","#cockpitInput { padding-left: 16px; }.cptOutput { overflow: auto; position: absolute; z-index: 999; display: none; }.cptCompletion { padding: 0; position: absolute; z-index: -1000; }.cptCompletion.VALID { background: #FFF; }.cptCompletion.INCOMPLETE { background: #DDD; }.cptCompletion.INVALID { background: #DDD; }.cptCompletion span { color: #FFF; }.cptCompletion span.INCOMPLETE { color: #DDD; border-bottom: 2px dotted #F80; }.cptCompletion span.INVALID { color: #DDD; border-bottom: 2px dotted #F00; }span.cptPrompt { color: #66F; font-weight: bold; }.cptHints { color: #000; position: absolute; border: 1px solid rgba(230, 230, 230, 0.8); background: rgba(250, 250, 250, 0.8); -moz-border-radius-topleft: 10px; -moz-border-radius-topright: 10px; border-top-left-radius: 10px; border-top-right-radius: 10px; z-index: 1000; padding: 8px; display: none;}.cptFocusPopup { display: block; }.cptFocusPopup.cptNoPopup { display: none; }.cptHints ul { margin: 0; padding: 0 15px; }.cptGt { font-weight: bold; font-size: 120%; }"),define("text!cockpit/ui/request_view.css",".cptRowIn { display: box; display: -moz-box; display: -webkit-box; box-orient: horizontal; -moz-box-orient: horizontal; -webkit-box-orient: horizontal; box-align: center; -moz-box-align: center; -webkit-box-align: center; color: #333; background-color: #EEE; width: 100%; font-family: consolas, courier, monospace;}.cptRowIn > * { padding-left: 2px; padding-right: 2px; }.cptRowIn > img { cursor: pointer; }.cptHover { display: none; }.cptRowIn:hover > .cptHover { display: block; }.cptRowIn:hover > .cptHover.cptHidden { display: none; }.cptOutTyped { box-flex: 1; -moz-box-flex: 1; -webkit-box-flex: 1; font-weight: bold; color: #000; font-size: 120%;}.cptRowOutput { padding-left: 10px; line-height: 1.2em; }.cptRowOutput strong,.cptRowOutput b,.cptRowOutput th,.cptRowOutput h1,.cptRowOutput h2,.cptRowOutput h3 { color: #000; }.cptRowOutput a { font-weight: bold; color: #666; text-decoration: none; }.cptRowOutput a: hover { text-decoration: underline; cursor: pointer; }.cptRowOutput input[type=password],.cptRowOutput input[type=text],.cptRowOutput textarea { color: #000; font-size: 120%; background: transparent; padding: 3px; border-radius: 5px; -moz-border-radius: 5px; -webkit-border-radius: 5px;}.cptRowOutput table,.cptRowOutput td,.cptRowOutput th { border: 0; padding: 0 2px; }.cptRowOutput .right { text-align: right; }"),define("text!cockpit/ui/request_view.html",'
    >
    ${request.typed}
    Hide command output Show command output Remove this command from the history
    '),define("text!cockpit/ui/images/closer.png","data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAj9JREFUeNp0ks+LUlEUx7/vV1o8Z8wUx3IEHcQmiBiQlomjRNCiZpEuEqF/oEUwq/6EhvoHggmRcJUQBM1CRJAW0aLIaGQimZJxJsWxyV/P9/R1zzWlFl04vPvOPZ9z7rnnK5imidmKRCIq+zxgdoPZ1T/ut8xeM3tcKpW6s1hhBkaj0Qj7bDebTX+324WmadxvsVigqipcLleN/d4rFoulORiLxTZY8ItOp8MBCpYkiYPj8Xjus9vtlORWoVB4KcTjcQc732dLpSRXvCZaAws6Q4WDdqsO52kNH+oCRFGEz+f7ydwBKRgMPmTXi49GI1x2D/DsznesB06ws2eDbI7w9HYN6bVjvGss4KAjwDAMq81mM2SW5Wa/3weBbz42UL9uYnVpiO2Nr9ANHSGXib2Wgm9tCYIggGKJEVkvlwgi5/FQRmTLxO6hgJVzI1x0T/fJrBtHJxPeL6tI/fsZLA6ot8lkQi8HRVbw94gkWYI5MaHrOjcCGSNRxZosy9y5cErDzn0Dqx7gcwO8WtBp4PndI35GMYqiUMUvBL5yOBz8yRfFNpbPmqgcCFh/IuHa1nR/YXGM8+oUpFhihEQiwcdRLpfVRqOBtWXWq34Gra6AXq8Hp2piZcmKT4cKnE4nwuHwdByVSmWQz+d32WCTlHG/qaHHREN9kgi0sYQfv0R4PB4EAgESQDKXy72fSy6VSnHJVatVf71eR7vd5n66mtfrRSgU4pLLZrOlf7RKK51Ok8g3/yPyR5lMZi7y3wIMAME4EigHWgKnAAAAAElFTkSuQmCC"),define("text!cockpit/ui/images/dot_clear.gif","data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAEBMgA7"),define("text!cockpit/ui/images/minus.png","data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAAZiS0dEANIA0gDS7KbF4AAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9kFGw4xMrIJw5EAAAHcSURBVCjPhZIxSxtxGMZ/976XhJA/RA5EAyJcFksnp64hjUPBoXRyCYLQTyD0UxScu0nFwalCQSgFCVk7dXAwUAiBDA2RO4W7yN1x9+9gcyhU+pteHt4H3pfncay1LOl0OgY4BN4Ar/7KP4BvwNFwOIyWu87S2O12O8DxfD73oygiSRIAarUaxhhWV1fHwMFgMBiWxl6v9y6Koi+3t7ckSUKtVkNVAcjzvNRWVlYwxry9vLz86uzs7HjAZDKZGGstjUaDfxHHMSLC5ubmHdB2VfVwNpuZ5clxHPMcRVFwc3PTXFtbO3RFZHexWJCmabnweAaoVqvlv4vFAhHZdVX1ZZqmOI5DURR8fz/lxbp9Yrz+7bD72SfPcwBU1XdF5N5aWy2KgqIoeBzPEnWVLMseYnAcRERdVR27rrsdxzGqyutP6898+GBsNBqo6i9XVS88z9sOggAR4X94noeqXoiIHPm+H9XrdYIgIAxDwjAkTVPCMESzBy3LMprNJr7v34nIkV5dXd2fn59fG2P2siwjSRIqlQrWWlSVJFcqlQqtVot2u40xZu/s7OxnWbl+v98BjkejkT+dTgmCoDxtY2ODra2tMXBweno6fNJVgP39fQN8eKbkH09OTsqS/wHFRdHPfTSfjwAAAABJRU5ErkJggg=="),define("text!cockpit/ui/images/pinaction.png","data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAC7mlDQ1BJQ0MgUHJvZmlsZQAAeAGFVM9rE0EU/jZuqdAiCFprDrJ4kCJJWatoRdQ2/RFiawzbH7ZFkGQzSdZuNuvuJrWliOTi0SreRe2hB/+AHnrwZC9KhVpFKN6rKGKhFy3xzW5MtqXqwM5+8943731vdt8ADXLSNPWABOQNx1KiEWlsfEJq/IgAjqIJQTQlVdvsTiQGQYNz+Xvn2HoPgVtWw3v7d7J3rZrStpoHhP1A4Eea2Sqw7xdxClkSAog836Epx3QI3+PY8uyPOU55eMG1Dys9xFkifEA1Lc5/TbhTzSXTQINIOJT1cVI+nNeLlNcdB2luZsbIEL1PkKa7zO6rYqGcTvYOkL2d9H5Os94+wiHCCxmtP0a4jZ71jNU/4mHhpObEhj0cGDX0+GAVtxqp+DXCFF8QTSeiVHHZLg3xmK79VvJKgnCQOMpkYYBzWkhP10xu+LqHBX0m1xOv4ndWUeF5jxNn3tTd70XaAq8wDh0MGgyaDUhQEEUEYZiwUECGPBoxNLJyPyOrBhuTezJ1JGq7dGJEsUF7Ntw9t1Gk3Tz+KCJxlEO1CJL8Qf4qr8lP5Xn5y1yw2Fb3lK2bmrry4DvF5Zm5Gh7X08jjc01efJXUdpNXR5aseXq8muwaP+xXlzHmgjWPxHOw+/EtX5XMlymMFMXjVfPqS4R1WjE3359sfzs94i7PLrXWc62JizdWm5dn/WpI++6qvJPmVflPXvXx/GfNxGPiKTEmdornIYmXxS7xkthLqwviYG3HCJ2VhinSbZH6JNVgYJq89S9dP1t4vUZ/DPVRlBnM0lSJ93/CKmQ0nbkOb/qP28f8F+T3iuefKAIvbODImbptU3HvEKFlpW5zrgIXv9F98LZua6N+OPwEWDyrFq1SNZ8gvAEcdod6HugpmNOWls05Uocsn5O66cpiUsxQ20NSUtcl12VLFrOZVWLpdtiZ0x1uHKE5QvfEp0plk/qv8RGw/bBS+fmsUtl+ThrWgZf6b8C8/UXAeIuJAAAACXBIWXMAAAsTAAALEwEAmpwYAAAClklEQVQ4EX1TXUhUQRQ+Z3Zmd+9uN1q2P3UpZaEwcikKekkqLKggKHJ96MHe9DmLkCDa9U198Id8kErICmIlRAN96UdE6QdBW/tBA5Uic7E0zN297L17p5mb1zYjD3eYc+d83zlnON8g5xzWNUSEdUBkHTJasRWySPP7fw3hfwkk2GoNsc0vOaJRHo1GV/GiMctkTIJRFlpZli8opK+htmf83gXeG63oteOtra0u25e7TYJIJELb26vYCACTgUe1lXV86BTn745l+MsyHqs53S/Aq4VEUa9Y6ko14eYY4u3AyM3HYwdKU35DZyblGR2+qq6W0X2Nnh07xynnVYpHORx/E1/GvvqaAZUayjMjdM2f/Lgr5E+fV93zR4u3zKCLughsZqKwAzAxaz6dPY6JgjLUF+eSP5OpjmAw2E8DvldHSvJMKPg08aRor1tc4BuALu6mOwGWdQC3mKIqRsC8mKd8wYfD78/earzSYzdMDW9QgKb0Is8CBY1mQXOiaXAHEpMDE5XTJqIq4EiyxUqKlpfkF0pyV1OTAoFAhmTmyCCoDsZNZvIkUjELQpipo0sQqYZAswZHwsEEE10M0pq2SSZY9HqNcDicJcNTpBvQJz40UbSOTh1B8bDpuY0w9Hb3kkn9lPAlBLfhfD39XTtX/blFJqiqrjbkTi63Hbofj2uL4GMsmzFgbDJ/vmMgv/lB4syJ0oXO7d3j++vio6GFsYmD6cHJreWc3/jRVVHhsOYvM8iZ36mtjPDBk/xDZE8CoHlbrlAssbTxDdDJvdb536L7I6S7Vy++6Gi4Xi9BsUthJRaLOYSPz4XALKI4j4iObd/e5UtDKUjZzYyYRyGAJv01Zj8kC5cbs5WY83hQnv0DzCXl+r8APElkq0RU6oMAAAAASUVORK5CYII="),define("text!cockpit/ui/images/pinin.png","data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAC7mlDQ1BJQ0MgUHJvZmlsZQAAeAGFVM9rE0EU/jZuqdAiCFprDrJ4kCJJWatoRdQ2/RFiawzbH7ZFkGQzSdZuNuvuJrWliOTi0SreRe2hB/+AHnrwZC9KhVpFKN6rKGKhFy3xzW5MtqXqwM5+8943731vdt8ADXLSNPWABOQNx1KiEWlsfEJq/IgAjqIJQTQlVdvsTiQGQYNz+Xvn2HoPgVtWw3v7d7J3rZrStpoHhP1A4Eea2Sqw7xdxClkSAog836Epx3QI3+PY8uyPOU55eMG1Dys9xFkifEA1Lc5/TbhTzSXTQINIOJT1cVI+nNeLlNcdB2luZsbIEL1PkKa7zO6rYqGcTvYOkL2d9H5Os94+wiHCCxmtP0a4jZ71jNU/4mHhpObEhj0cGDX0+GAVtxqp+DXCFF8QTSeiVHHZLg3xmK79VvJKgnCQOMpkYYBzWkhP10xu+LqHBX0m1xOv4ndWUeF5jxNn3tTd70XaAq8wDh0MGgyaDUhQEEUEYZiwUECGPBoxNLJyPyOrBhuTezJ1JGq7dGJEsUF7Ntw9t1Gk3Tz+KCJxlEO1CJL8Qf4qr8lP5Xn5y1yw2Fb3lK2bmrry4DvF5Zm5Gh7X08jjc01efJXUdpNXR5aseXq8muwaP+xXlzHmgjWPxHOw+/EtX5XMlymMFMXjVfPqS4R1WjE3359sfzs94i7PLrXWc62JizdWm5dn/WpI++6qvJPmVflPXvXx/GfNxGPiKTEmdornIYmXxS7xkthLqwviYG3HCJ2VhinSbZH6JNVgYJq89S9dP1t4vUZ/DPVRlBnM0lSJ93/CKmQ0nbkOb/qP28f8F+T3iuefKAIvbODImbptU3HvEKFlpW5zrgIXv9F98LZua6N+OPwEWDyrFq1SNZ8gvAEcdod6HugpmNOWls05Uocsn5O66cpiUsxQ20NSUtcl12VLFrOZVWLpdtiZ0x1uHKE5QvfEp0plk/qv8RGw/bBS+fmsUtl+ThrWgZf6b8C8/UXAeIuJAAAACXBIWXMAAAsTAAALEwEAmpwYAAABZ0lEQVQ4Ea2TPUsDQRCGZ89Eo4FACkULEQs1CH4Uamfjn7GxEYJFIFXgChFsbPwzNnZioREkaiHBQtEiEEiMRm/dZ8OEGAxR4sBxx877Pju7M2estTJIxLrNuVwuMxQEx0ZkzcFHyRtjXt02559RtB2GYanTYzoryOfz+6l4Nbszf2niwffKmpGRo9sVW22mDgqFwp5C2gDMm+P32a3JB1N+n5JifUGeP9JeNxGryPLYjcwMP8rJ07Q9fZltQzyAstOJ2vVu5sKc1ZZkRBrOcKeb+HexPidvkpCN5JUcllZtpZFc5DgBWc5M2eysZuMuofMBSA4NWjx4PUCsXefMlI0QY3ewRg4NWi4ZTQsgrjYXema+e4VqtEMK6KXvu+4B9Bklt90vVKMeD2BI6DOt4rZ/Gk7WyKFBi4fNPIAJY0joM61SCCZ9tI1o0OIB8D+DBIkYaJRbCBH9mZgNt+bb++ufSSF/eX8BYcDeAzuQJVUAAAAASUVORK5CYII="),define("text!cockpit/ui/images/pinout.png","data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAC7mlDQ1BJQ0MgUHJvZmlsZQAAeAGFVM9rE0EU/jZuqdAiCFprDrJ4kCJJWatoRdQ2/RFiawzbH7ZFkGQzSdZuNuvuJrWliOTi0SreRe2hB/+AHnrwZC9KhVpFKN6rKGKhFy3xzW5MtqXqwM5+8943731vdt8ADXLSNPWABOQNx1KiEWlsfEJq/IgAjqIJQTQlVdvsTiQGQYNz+Xvn2HoPgVtWw3v7d7J3rZrStpoHhP1A4Eea2Sqw7xdxClkSAog836Epx3QI3+PY8uyPOU55eMG1Dys9xFkifEA1Lc5/TbhTzSXTQINIOJT1cVI+nNeLlNcdB2luZsbIEL1PkKa7zO6rYqGcTvYOkL2d9H5Os94+wiHCCxmtP0a4jZ71jNU/4mHhpObEhj0cGDX0+GAVtxqp+DXCFF8QTSeiVHHZLg3xmK79VvJKgnCQOMpkYYBzWkhP10xu+LqHBX0m1xOv4ndWUeF5jxNn3tTd70XaAq8wDh0MGgyaDUhQEEUEYZiwUECGPBoxNLJyPyOrBhuTezJ1JGq7dGJEsUF7Ntw9t1Gk3Tz+KCJxlEO1CJL8Qf4qr8lP5Xn5y1yw2Fb3lK2bmrry4DvF5Zm5Gh7X08jjc01efJXUdpNXR5aseXq8muwaP+xXlzHmgjWPxHOw+/EtX5XMlymMFMXjVfPqS4R1WjE3359sfzs94i7PLrXWc62JizdWm5dn/WpI++6qvJPmVflPXvXx/GfNxGPiKTEmdornIYmXxS7xkthLqwviYG3HCJ2VhinSbZH6JNVgYJq89S9dP1t4vUZ/DPVRlBnM0lSJ93/CKmQ0nbkOb/qP28f8F+T3iuefKAIvbODImbptU3HvEKFlpW5zrgIXv9F98LZua6N+OPwEWDyrFq1SNZ8gvAEcdod6HugpmNOWls05Uocsn5O66cpiUsxQ20NSUtcl12VLFrOZVWLpdtiZ0x1uHKE5QvfEp0plk/qv8RGw/bBS+fmsUtl+ThrWgZf6b8C8/UXAeIuJAAAACXBIWXMAAAsTAAALEwEAmpwYAAACyUlEQVQ4EW1TXUgUURQ+Z3ZmnVV3QV2xJbVSEIowQbAfLQx8McLoYX2qjB58MRSkP3vZppceYhGxgrZaIughlYpE7CHFWiiKyj9II0qxWmwlNh1Xtp2f27mz7GDlZX7uuXO+73zfuXeQMQYIgAyALppgyBtse32stsw86txkHhATn+FbfPfzxnPB+vR3RMJYuTwW6bbB4a6WS5O3Yu2VlXIesDiAamiQNKVlVXfx5I0GJ7DY7p0/+erU4dgeMJIA31WNxZmAgibOreXDqF55sY4SFUURqbi+nkjgwTyAbHhLX8yOLsSM2QRA3JRAAgd4RGPbVhkKEp8qeJ7PFyW3fw++YHtC7CkaD0amqyqihSwlMQQ0wa07IjPVI/vbexreIUrVaQV2D4RMQ/o7m12Mdfx4H3PfB9FNzTR1U2cO0Bi45aV6xNvFBNaoIAfbSiwLlqi9/hR/R3Nrhua+Oqi9TEKiB02C7YXz+Pba4MTDrpbLiMAxNgmXb+HpwVkZdoIrkn9isW7nRw/TZYaagZArAWyhfqsSDL/c9aTx7JUjGZCtYExRqCzAwGblwr6aFQ84nTo6qZ7XCeCVQNckE/KSWolvoQnxeoFFgIh8G/nA+kBAxxuQO5m9eFrwLIGJHgcyM63VFMhRSgNVyJr7og8y1vbTQpH8DIEVgxuYuexw0QECIalq5FYgEmpkgoFYltU/lnrqDz5osirSFpF7lrHAFKSWHYfEs+mY/82UnAStyMlW8sUPsVIciTZgz3jV1ebg0CEOpgPF22s1z1YQYKSXPJ1hbAhR8T26WdLhkuVfAzPR+YO1Ox5n58SmCcF6e3uzAoHA77RkevJdWH/3+f2O9TGf3w3fWQ2Hw5F/13mcsWAT+vv6DK4kFApJ/d3d1k+kJtbCrmxXHS3n8ER6b3CQbAqaEHVra6sGxcXW4SovLx+empxapS//FfwD9kpMJjMMBBAAAAAASUVORK5CYII="),define("text!cockpit/ui/images/pins.png","data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAQCAYAAABQrvyxAAAACXBIWXMAAAsTAAALEwEAmpwYAAAGYklEQVRIDbVWe0yURxCf/R735o6DO0FBe0RFsaL4iLXGIKa2SY3P6JGa2GpjlJjUV9NosbU++tYUbEnaQIrVaKJBG7WiNFQFUWO1UUEsVg2CAgoeHHLewcH32O58cBdQsX9Y5+7LfrszOzO/2ZnZj1BKgTBiIwVGVvKd49OVVYunDlXn6wdBKh+ogXrv+DOz1melIb+3LM5fNv2XPYE5EHY+L3PJljN5zavHpJjsQNsA/JJEgyC2+WTjy3b0GfoJW8O4aoHtDwiHQrj5lw1LLyyb1bp5zAjJTus9klrVpdD6TqH2ngVO+0dsRJnp06cLIYU4fx7NnRI3bu7UIYOeJ/McnuY88q3k62gc0S4Dgf5qhICQtIXS2lqD7BhSduPk3YfyzXaANhBBJDxYdUqCywB2qS4RdyUuSkTF/VJxcbH5j8N7/75RuFrN3Zh8OS8zqf5m4UpPeenOyP42dbtBeuvVnCdkK1e4PfPouX03mo9se+c33M8wqDk5Ofqed8REUTicQhbySUxp9u3KlMSHTtrFU6Kyn03lz15PPpW25vsZeYSIKyiVURcqeZJOH9lTNZLfnxRjU/uwrjbEUBWsapcSO2Hq4k0VfZg9EzxdDNCEjDxgNqRDme9umz/btwlsHRIEePHgAf73RdnHZ6LTuIUBN7OBQ+c1Fdnp6cZ1BQUdeRuWZi97o3ktDQQkVeFFzqJARd1A5a0Vr7ta6Kp6TZjtZ+NTIOoKF6qDrL7e0QQIUCiqMMKk8Z1Q/SCSKvzocf2B6NEN0SQn/kTO6fKJ0zqjZUlQBSpJ0GjR77w0aoc1Pr6S5/kVJrNpakV5hR+LWKN4t7sLX+p0rx2vqSta64olIulUKUgCSXLWE1R4KPPSj+5vhm2hdDOG+CkQBmhhyyKq6SaFYWTn5bB3QJRNz54AuXKn8TJjhu0Wbv+wNEKQjVhnmKopjo4FxXmetCRnC4F7BhCiCUepqAepRh0TM/gjjzOOSK2NgWZPc05qampRWJHb7dbOffep2ednzLzgczlbrQA6gHYF9BYDh9GY+FjddMweHMscmMuep07gXlMQoqw9ALoYu5MJsak9QmJA2IvAgVmoCRciooyPujJtNCv1uHt3TmK9gegFKrG9kh6oXwZiIEAtBIjORGKNTWR/WeW8XVkbjuJepLAyloM8LmTN//njKZPbraATZaLjCHEww9Ei4FFiPg6Ja5gT6gxYgLgnRDHRQwJXbz2GOw0d4A3K4GXlUtMahJjYVxiYbrwOmxIS10bFnIBOSi6Tl9Jgs0zbOEX18wyEwgLPMrxD1Y4aCK8kmTpgYcpAF27Mzs42Hjx4kA8BICUlJfKArR7LcEvTB1xEC9AoEw9OPagWkVU/D1oesmK6U911zEczMVe01oZjiMggg6ux2Qk379qh4rYKet4GjrhhwEteBgBrH8BssoXEtbHzPpSBRRSpqlNpgAiUoxzHKxLRszoVuggIisxaDQWZqkQvQjAoax3NbDbLLGuUEABNGedXqSyLRupXgDT5JfAGZNLio9B0X8Uiwk4w77MDc1D4yejjWtykPS3DX01UDCY/GPQcVDe0QYT0CIxGFvUorfvBxZsRfVrUuWruMBAb/lXCUofoFNZfzGJtowXOX0vwUSFK4BgyMKm6P6s9wQUZld+jrYyMDC0iIQDaJdG4IyZQfL3RfbFcCBIlRgc+u3CjaTApuZ9KsANgG8PNzHlWWD3tCxd6kafNNiFp5HAalAkkJ0SCV2H3CgOD9Nc/FqrXuyb0Eocvfhq171p5eyuJ1omKJEP5rQGe/FOOnXtq335z8YmvYo9cHb2t8spIb3lVSseZW46FlGY/Sk9P50P2w20UlWJUkUHIushfc5PXGAzCo0PlD2pnpCYfCXga3lu+fPlevEhWrVrFyrN/Orfv87FOW9tlqb2Kc9pV8DzioMk3UNUbXM+8B/ATBr8C8CKdvGXWGD/9sqm3dkxtzA4McMjHMB8D2ftheYXo+qzt3pXvz8/PP/vk+v8537V+yYW87Zu+RZ1ZbrexoKAA/SBpaWn4+aL5w5zGk+/jW59JiMkESW5urpiVlWXENRb1H/Yf2I9txIxz5IdkX3TsraukpsbQjz6090yb4XsAvQoRE0YvJdamtIIbOnRoUVlZ2ftsLVQzIdEXHntsaZdimssVfCpFui109+BnWPsXaWLI/zactygAAAAASUVORK5CYII="),define("text!cockpit/ui/images/plus.png","data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAAZiS0dEANIA0gDS7KbF4AAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9kFGw4yFTwuJTkAAAH7SURBVCjPdZKxa1NRFMZ/956XZMgFyyMlCZRA4hBx6lBcQ00GoYi4tEstFPwLAs7iLDi7FWuHThaUggihBDI5OWRoQAmBQFISQgvvpbwX3rsOaR4K+o2H8zvfOZxPWWtZqVarGaAJPAEe3ZW/A1+Bd+1221v1qhW4vb1dA44mk0nZ8zyCIAAgk8lgjGF9fb0PHF5cXLQTsF6vP/c879P19TVBEJDJZBARAKIoSmpra2sYY561Wq3PqtFouMBgMBgYay3ZbJZ/yfd9tNaUSqUboOKISPPq6sqsVvZ9H4AvL34B8PTj/QSO45jpdHovn883Ha31znw+JwzDpCEMQx4UloM8zyOdTif3zudztNY7jog8DMMQpRRxHPPt5TCBAEZvxlyOFTsfykRRBICIlB2t9a21Nh3HMXEc8+d7VhJHWCwWyzcohdZaHBHpO46z6fs+IsLj94XECaD4unCHL8FsNouI/HRE5Nx13c3ZbIbWOnG5HKtl+53TSq7rIiLnand31wUGnU7HjEYjlFLJZN/3yRnL1FMYY8jlcmxtbd0AFel2u7dnZ2eXxpi9xWJBEASkUimstYgIQSSkUimKxSKVSgVjzN7p6emPJHL7+/s14KjX65WHwyGz2SxZbWNjg2q12gcOT05O2n9lFeDg4MAAr/4T8rfHx8dJyH8DvvbYGzKvWukAAAAASUVORK5CYII="),define("text!cockpit/ui/images/throbber.gif","data:image/gif;base64,R0lGODlh3AATAPQAAP///wAAAL6+vqamppycnLi4uLKyssjIyNjY2MTExNTU1Nzc3ODg4OTk5LCwsLy8vOjo6Ozs7MrKyvLy8vT09M7Ozvb29sbGxtDQ0O7u7tbW1sLCwqqqqvj4+KCgoJaWliH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCgAAACwAAAAA3AATAAAF/yAgjmRpnmiqrmzrvnAsz3Rt33iu73zv/8CgcEgECAaEpHLJbDqf0Kh0Sq1ar9isdjoQtAQFg8PwKIMHnLF63N2438f0mv1I2O8buXjvaOPtaHx7fn96goR4hmuId4qDdX95c4+RG4GCBoyAjpmQhZN0YGYFXitdZBIVGAoKoq4CG6Qaswi1CBtkcG6ytrYJubq8vbfAcMK9v7q7D8O1ycrHvsW6zcTKsczNz8HZw9vG3cjTsMIYqQgDLAQGCQoLDA0QCwUHqfYSFw/xEPz88/X38Onr14+Bp4ADCco7eC8hQYMAEe57yNCew4IVBU7EGNDiRn8Z831cGLHhSIgdE/9chIeBgDoB7gjaWUWTlYAFE3LqzDCTlc9WOHfm7PkTqNCh54rePDqB6M+lR536hCpUqs2gVZM+xbrTqtGoWqdy1emValeXKwgcWABB5y1acFNZmEvXwoJ2cGfJrTv3bl69Ffj2xZt3L1+/fw3XRVw4sGDGcR0fJhxZsF3KtBTThZxZ8mLMgC3fRatCLYMIFCzwLEprg84OsDus/tvqdezZf13Hvr2B9Szdu2X3pg18N+68xXn7rh1c+PLksI/Dhe6cuO3ow3NfV92bdArTqC2Ebc3A8vjf5QWf15Bg7Nz17c2fj69+fnq+8N2Lty+fuP78/eV2X13neIcCeBRwxorbZrAxAJoCDHbgoG8RTshahQ9iSKEEzUmYIYfNWViUhheCGJyIP5E4oom7WWjgCeBBAJNv1DVV01MZdJhhjdkplWNzO/5oXI846njjVEIqR2OS2B1pE5PVscajkxhMycqLJgxQCwT40PjfAV4GqNSXYdZXJn5gSkmmmmJu1aZYb14V51do+pTOCmA00AqVB4hG5IJ9PvYnhIFOxmdqhpaI6GeHCtpooisuutmg+Eg62KOMKuqoTaXgicQWoIYq6qiklmoqFV0UoeqqrLbq6quwxirrrLTWauutJ4QAACH5BAkKAAAALAAAAADcABMAAAX/ICCOZGmeaKqubOu+cCzPdG3feK7vfO//wKBwSAQIBoSkcslsOp/QqHRKrVqv2Kx2OhC0BAXHx/EoCzboAcdhcLDdgwJ6nua03YZ8PMFPoBMca215eg98G36IgYNvDgOGh4lqjHd7fXOTjYV9nItvhJaIfYF4jXuIf4CCbHmOBZySdoOtj5eja59wBmYFXitdHhwSFRgKxhobBgUPAmdoyxoI0tPJaM5+u9PaCQZzZ9gP2tPcdM7L4tLVznPn6OQb18nh6NV0fu3i5OvP8/nd1qjwaasHcIPAcf/gBSyAAMMwBANYEAhWYQGDBhAyLihwYJiEjx8fYMxIcsGDAxVA/yYIOZIkBAaGPIK8INJlRpgrPeasaRPmx5QgJfB0abLjz50tSeIM+pFmUo0nQQIV+vRlTJUSnNq0KlXCSq09ozIFexEBAYkeNiwgOaEtn2LFpGEQsKCtXbcSjOmVlqDuhAx3+eg1Jo3u37sZBA9GoMAw4MB5FyMwfLht4sh7G/utPGHlYAV8Nz9OnOBz4c2VFWem/Pivar0aKCP2LFn2XwhnVxBwsPbuBAQbEGiIFg1BggoWkidva5z4cL7IlStfkED48OIYoiufYIH68+cKPkqfnsB58ePjmZd3Dj199/XE20tv6/27XO3S6z9nPCz9BP3FISDefL/Bt192/uWmAv8BFzAQAQUWWFaaBgqA11hbHWTIXWIVXifNhRlq6FqF1sm1QQYhdiAhbNEYc2KKK1pXnAIvhrjhBh0KxxiINlqQAY4UXjdcjSJyeAx2G2BYJJD7NZQkjCPKuCORKnbAIXsuKhlhBxEomAIBBzgIYXIfHfmhAAyMR2ZkHk62gJoWlNlhi33ZJZ2cQiKTJoG05Wjcm3xith9dcOK5X51tLRenoHTuud2iMnaolp3KGXrdBo7eKYF5p/mXgJcogClmcgzAR5gCKymXYqlCgmacdhp2UCqL96mq4nuDBTmgBasaCFp4sHaQHHUsGvNRiiGyep1exyIra2mS7dprrtA5++z/Z8ZKYGuGsy6GqgTIDvupRGE+6CO0x3xI5Y2mOTkBjD4ySeGU79o44mcaSEClhglgsKyJ9S5ZTGY0Bnzrj+3SiKK9Rh5zjAALCywZBk/ayCWO3hYM5Y8Dn6qxxRFsgAGoJwwgDQRtYXAAragyQOmaLKNZKGaEuUlpyiub+ad/KtPqpntypvvnzR30DBtjMhNodK6Eqrl0zU0/GjTUgG43wdN6Ra2pAhGtAAZGE5Ta8TH6wknd2IytNKaiZ+Or79oR/tcvthIcAPe7DGAs9Edwk6r3qWoTaNzY2fb9HuHh2S343Hs1VIHhYtOt+Hh551rh24vP5YvXSGzh+eeghy76GuikU9FFEainrvrqrLfu+uuwxy777LTXfkIIACH5BAkKAAAALAAAAADcABMAAAX/ICCOZGmeaKqubOu+cCzPdG3feK7vfO//wKBwSAQIBoSkcslsOp/QqHRKrVqv2Kx2OhC0BAWHB2l4CDZo9IDjcBja7UEhTV+3DXi3PJFA8xMcbHiDBgMPG31pgHBvg4Z9iYiBjYx7kWocb26OD398mI2EhoiegJlud4UFiZ5sm6Kdn2mBr5t7pJ9rlG0cHg5gXitdaxwFGArIGgoaGwYCZ3QFDwjU1AoIzdCQzdPV1c0bZ9vS3tUJBmjQaGXl1OB0feze1+faiBvk8wjnimn55e/o4OtWjp+4NPIKogsXjaA3g/fiGZBQAcEAFgQGOChgYEEDCCBBLihwQILJkxIe/3wMKfJBSQkJYJpUyRIkgwcVUJq8QLPmTYoyY6ZcyfJmTp08iYZc8MBkhZgxk9aEcPOlzp5FmwI9KdWn1qASurJkClRoWKwhq6IUqpJBAwQEMBYroAHkhLt3+RyzhgCDgAV48Wbgg+waAnoLMgTOm6DwQ8CLBzdGdvjw38V5JTg2lzhyTMeUEwBWHPgzZc4TSOM1bZia6LuqJxCmnOxv7NSsl1mGHHiw5tOuIWeAEHcFATwJME/ApgFBc3MVLEgPvE+Ddb4JokufPmFBAuvPXWu3MIF89wTOmxvOvp179evQtwf2nr6aApPyzVd3jn089e/8xdfeXe/xdZ9/d1ngHf98lbHH3V0LMrgPgsWpcFwBEFBgHmyNXWeYAgLc1UF5sG2wTHjIhNjBiIKZCN81GGyQwYq9uajeMiBOQGOLJ1KjTI40kmfBYNfc2NcGIpI4pI0vyrhjiT1WFqOOLEIZnjVOVpmajYfBiCSNLGbA5YdOkjdihSkQwIEEEWg4nQUmvYhYe+bFKaFodN5lp3rKvJYfnBKAJ+gGDMi3mmbwWYfng7IheuWihu5p32XcSWdSj+stkF95dp64jJ+RBipocHkCCp6PCiRQ6INookCAAwy0yd2CtNET3Yo7RvihBjFZAOaKDHT43DL4BQnsZMo8xx6uI1oQrHXXhHZrB28G62n/YSYxi+uzP2IrgbbHbiaer7hCiOxDFWhrbmGnLVuus5NFexhFuHLX6gkEECorlLpZo0CWJG4pLjIACykmBsp0eSSVeC15TDJeUhlkowlL+SWLNJpW2WEF87urXzNWSZ6JOEb7b8g1brZMjCg3ezBtWKKc4MvyEtwybPeaMAA1ECRoAQYHYLpbeYYCLfQ+mtL5c9CnfQpYpUtHOSejEgT9ogZ/GSqd0f2m+LR5WzOtHqlQX1pYwpC+WbXKqSYtpJ5Mt4a01lGzS3akF60AxkcTaLgAyRBPWCoDgHfJqwRuBuzdw/1ml3iCwTIeLUWJN0v4McMe7uasCTxseNWPSxc5RbvIgD7geZLbGrqCG3jepUmbbze63Y6fvjiOylbwOITPfIHEFsAHL/zwxBdvPBVdFKH88sw37/zz0Ecv/fTUV2/99SeEAAAh+QQJCgAAACwAAAAA3AATAAAF/yAgjmRpnmiqrmzrvnAsz3Rt33iu73zv/8CgcEgECAaEpHLJbDqf0Kh0Sq1ar9isdjoQtAQFh2cw8BQEm3T6yHEYHHD4oKCuD9qGvNsxT6QTgAkcHHmFeX11fm17hXwPG35qgnhxbwMPkXaLhgZ9gWp3bpyegX4DcG+inY+Qn6eclpiZkHh6epetgLSUcBxlD2csXXdvBQrHGgoaGhsGaIkFDwjTCArTzX+QadHU3c1ofpHc3dcGG89/4+TYktvS1NYI7OHu3fEJ5tpqBu/k+HX7+nXDB06SuoHm0KXhR65cQT8P3FRAMIAFgVMPwDCAwLHjggIHJIgceeFBg44eC/+ITCCBZYKSJ1FCWPBgpE2YMmc+qNCypwScMmnaXAkUJYOaFVyKLOqx5tCXJnMelcBzJNSYKIX2ZPkzqsyjPLku9Zr1QciVErYxaICAgEUOBRJIgzChbt0MLOPFwyBggV27eCUcmxZvg9+/dfPGo5bg8N/Ag61ZM4w4seDF1fpWhizZmoa+GSortgcaMWd/fkP/HY0MgWbTipVV++wY8GhvqSG4XUEgoYTKE+Qh0OCvggULiBckWEZ4Ggbjx5HXVc58IPQJ0idQJ66XanTpFraTe348+XLizRNcz658eHMN3rNPT+C+G/nodqk3t6a+fN3j+u0Xn3nVTQPfdRPspkL/b+dEIN8EeMm2GAYbTNABdrbJ1hyFFv5lQYTodSZABhc+loCEyhxTYYkZopdMMiNeiBxyIFajV4wYHpfBBspUl8yKHu6ooV5APsZjQxyyeNeJ3N1IYod38cgdPBUid6GCKfRWgAYU4IccSyHew8B3doGJHmMLkGkZcynKk2Z50Ym0zJzLbDCmfBbI6eIyCdyJmJmoqZmnBAXy9+Z/yOlZDZpwYihnj7IZpuYEevrYJ5mJEuqiof4l+NYDEXQpXQcMnNjZNDx1oGqJ4S2nF3EsqWrhqqVWl6JIslpAK5MaIqDeqjJq56qN1aTaQaPbHTPYr8Be6Gsyyh6Da7OkmmqP/7GyztdrNVQBm5+pgw3X7aoYKhfZosb6hyUKBHCgQKij1rghkOAJuZg1SeYIIY+nIpDvf/sqm4yNG5CY64f87qdAwSXKGqFkhPH1ZHb2EgYtw3bpKGVkPz5pJAav+gukjB1UHE/HLNJobWcSX8jiuicMMBFd2OmKwQFs2tjXpDfnPE1j30V3c7iRHlrzBD2HONzODyZtsQJMI4r0AUNaE3XNHQw95c9GC001MpIxDacFQ+ulTNTZlU3O1eWVHa6vb/pnQUUrgHHSBKIuwG+bCPyEqbAg25gMVV1iOB/IGh5YOKLKIQ6xBAcUHmzjIcIqgajZ+Ro42DcvXl7j0U4WOUd+2IGu7DWjI1pt4DYq8BPm0entuGSQY/4tBi9Ss0HqfwngBQtHbCH88MQXb/zxyFfRRRHMN+/889BHL/301Fdv/fXYZ39CCAAh+QQJCgAAACwAAAAA3AATAAAF/yAgjmRpnmiqrmzrvnAsz3Rt33iu73zv/8CgcEgECAaEpHLJbDqf0Kh0Sq1ar9isdjoQtAQFh2fAKXsKm7R6Q+Y43vABep0mGwwOPH7w2CT+gHZ3d3lyagl+CQNvg4yGh36LcHoGfHR/ZYOElQ9/a4ocmoRygIiRk5p8pYmZjXePaYBujHoOqp5qZHBlHAUFXitddg8PBg8KGsgayxvGkAkFDwgICtPTzX2mftHW3QnOpojG3dbYkNjk1waxsdDS1N7ga9zw1t/aifTk35fu6Qj3numL14fOuHTNECHqU4DDgQEsCCwidiHBAwYQMmpcUOCAhI8gJVzUuLGThAQnP/9abEAyI4MCIVOKZNnyJUqUJxNcGNlywYOQgHZirGkSJ8gHNEky+AkS58qWEJYC/bMzacmbQHkqNdlUJ1KoSz2i9COhmQYCEXtVrCBgwYS3cCf8qTcNQ9u4cFFOq2bPLV65Cf7dxZthbjW+CgbjnWtNgWPFcAsHdoxgWWK/iyV045sAc2S96SDn1exYw17REwpLQEYt2eW/qtPZRQAB7QoC61RW+GsBwYZ/CXb/XRCYLsAKFizEtUAc+G7lcZsjroscOvTmsoUvx15PwccJ0N8yL17N9PG/E7jv9S4hOV7pdIPDdZ+ePDzv2qMXn2b5+wTbKuAWnF3oZbABZY0lVmD/ApQd9thybxno2GGuCVDggaUpoyBsB1bGGgIYbJCBcuFJiOAyGohIInQSmmdeiBnMF2GHfNUlIoc1rncjYRjW6NgGf3VQGILWwNjBfxEZcAFbC7gHXQcfUYOYdwzQNxo5yUhQZXhvRYlMeVSuSOJHKJa5AQMQThBlZWZ6Bp4Fa1qzTAJbijcBlJrtxeaZ4lnnpZwpukWieGQmYx5ATXIplwTL8DdNZ07CtWYybNIJF4Ap4NZHe0920AEDk035kafieQrqXofK5ympn5JHKYjPrfoWcR8WWQGp4Ul32KPVgXdnqxM6OKqspjIYrGPDrlrsZtRIcOuR86nHFwbPvmes/6PH4frrqbvySh+mKGhaAARPzjjdhCramdoGGOhp44i+zogBkSDuWC5KlE4r4pHJkarXrj++Raq5iLmWLlxHBteavjG+6amJrUkJJI4Ro5sBv9AaOK+jAau77sbH7nspCwNIYIACffL7J4JtWQnen421nNzMcB6AqpRa9klonmBSiR4GNi+cJZpvwgX0ejj71W9yR+eIgaVvQgf0l/A8nWjUFhwtZYWC4hVnkZ3p/PJqNQ5NnwUQrQCGBBBMQIGTtL7abK+5JjAv1fi9bS0GLlJHgdjEgYzzARTwC1fgEWdJuKKBZzj331Y23qB3i9v5aY/rSUC4w7PaLeWXmr9NszMFoN79eeiM232o33EJAIzaSGwh++y012777bhT0UURvPfu++/ABy/88MQXb/zxyCd/QggAIfkECQoAAAAsAAAAANwAEwAABf8gII5kaZ5oqq5s675wLM90bd94ru987//AoHBIBAgGhKRyyWw6n9CodEqtWq/YrHY6ELQEBY5nwCk7xIWNer0hO95wziC9Ttg5b4ND/+Y87IBqZAaEe29zGwmJigmDfHoGiImTjXiQhJEPdYyWhXwDmpuVmHwOoHZqjI6kZ3+MqhyemJKAdo6Ge3OKbEd4ZRwFBV4rc4MPrgYPChrMzAgbyZSJBcoI1tfQoYsJydfe2amT3d7W0OGp1OTl0YtqyQrq0Lt11PDk3KGoG+nxBpvTD9QhwCctm0BzbOyMIwdOUwEDEgawIOCB2oMLgB4wgMCx44IHBySIHClBY0ePfyT/JCB5weRJCAwejFw58kGDlzBTqqTZcuPLmCIBiWx58+VHmiRLFj0JVCVLl0xl7qSZwCbOo0lFWv0pdefQrVFDJtr5gMBEYBgxqBWwYILbtxPsqMPAFu7blfa81bUbN4HAvXAzyLWnoDBguHIRFF6m4LBbwQngMYPXuC3fldbyPrMcGLM3w5wRS1iWWUNlvnElKDZtz/EEwaqvYahQoexEfyILi4RrYYKFZwJ3810QWZ2ECrx9Ew+O3K6F5Yq9zXbb+y30a7olJJ+wnLC16W97Py+uwdtx1NcLWzs/3G9e07stVPc9kHJ0BcLtQp+c3ewKAgYkUAFpCaAmmHqKLSYA/18WHEiZPRhsQF1nlLFWmIR8ZbDBYs0YZuCGpGXWmG92aWiPMwhEOOEEHXRwIALlwXjhio+BeE15IzpnInaLbZBBhhti9x2GbnVQo2Y9ZuCfCgBeMCB+DJDIolt4iVhOaNSJdCOBUfIlkmkyMpPAAvKJ59aXzTQzJo0WoJnmQF36Jp6W1qC4gWW9GZladCiyJd+KnsHImgRRVjfnaDEKuiZvbcYWo5htzefbl5LFWNeSKQAo1QXasdhiiwwUl2B21H3aQaghXnPcp1NagCqYslXAqnV+zYWcpNwVp9l5eepJnHqL4SdBi56CGlmw2Zn6aaiZjZqfb8Y2m+Cz1O0n3f+tnvrGbF6kToApCgAWoNWPeh754JA0vmajiAr4iOuOW7abQXVGNriBWoRdOK8FxNqLwX3oluubhv8yluRbegqGb536ykesuoXhyJqPQJIGbLvQhkcwjKs1zBvBwSZIsbcsDCCBAAf4ya+UEhyQoIiEJtfoZ7oxUOafE2BwgMWMqUydfC1LVtiArk0QtGkWEopzlqM9aJrKHfw5c6wKjFkmXDrbhwFockodtMGFLWpXy9JdiXN1ZDNszV4WSLQCGBKoQYHUyonqrHa4ErewAgMmcAAF7f2baIoVzC2p3gUvJtLcvIWqloy6/R04mIpLwDhciI8qLOB5yud44pHPLbA83hFDWPjNbuk9KnySN57Av+TMBvgEAgzzNhJb5K777rz37vvvVHRRxPDEF2/88cgnr/zyzDfv/PPQnxACACH5BAkKAAAALAAAAADcABMAAAX/ICCOZGmeaKqubOu+cCzPdG3feK7vfO//wKBwSAQIBoSkcslsOp/QqHRKrVqv2Kx2OhC0BIUCwcMpO84OT2HDbm8GHLQjnn6wE3g83SA3DB55G3llfHxnfnZ4gglvew6Gf4ySgmYGlpCJknochWiId3kJcZZyDn93i6KPl4eniopwq6SIoZKxhpenbhtHZRxhXisDopwPgHkGDxrLGgjLG8mC0gkFDwjX2AgJ0bXJ2djbgNJsAtbfCNB2oOnn6MmKbeXt226K1fMGi6j359D69ua+QZskjd+3cOvY9XNgp4ABCQNYEDBl7EIeCQkeMIDAseOCBwckiBSZ4ILGjh4B/40kaXIjSggMHmBcifHky5gYE6zM2OAlzGM6Z5rs+fIjTZ0tfcYMSlLCUJ8fL47kCVXmTjwPiKJkUCDnyqc3CxzQmYeAxAEGLGJYiwCDgAUT4sqdgOebArdw507IUNfuW71xdZ7DC5iuhGsKErf9CxhPYgUaEhPWyzfBMgUIJDPW6zhb5M1y+R5GjFkBaLmCM0dOfHqvztXYJnMejaFCBQlmVxAYsEGkYnQV4lqYMNyCtnYSggNekAC58uJxmTufW5w55mwKkg+nLp105uTC53a/nhg88fMTmDfDVl65Xum/IZt/3/zaag3a5W63nll1dvfiWbaaZLmpQIABCVQA2f9lAhTG112PQWYadXE9+FtmEwKWwQYQJrZagxomsOCAGVImInsSbpCBhhwug6KKcXXQQYUcYuDMggrASFmNzjjzzIrh7cUhhhHqONeGpSEW2QYxHsmjhxpgUGAKB16g4IIbMNCkXMlhaJ8GWVJo2I3NyKclYF1GxgyYDEAnXHJrMpNAm/rFBSczPiYAlwXF8ZnmesvoOdyMbx7m4o0S5LWdn4bex2Z4xYmEzaEb5EUcnxbA+WWglqIn6aHPTInCgVbdlZyMqMrIQHMRSiaBBakS1903p04w434n0loBoQFOt1yu2YAnY68RXiNsqh2s2qqxuyKb7Imtmgcrqsp6h8D/fMSpapldx55nwayK/SfqCQd2hcFdAgDp5GMvqhvakF4mZuS710WGIYy30khekRkMu92GNu6bo7r/ttjqwLaua5+HOdrKq5Cl3dcwi+xKiLBwwwom4b0E6xvuYyqOa8IAEghwQAV45VvovpkxBl2mo0W7AKbCZXoAhgMmWnOkEqx2JX5nUufbgJHpXCfMOGu2QAd8eitpW1eaNrNeMGN27mNz0swziYnpSbXN19gYtstzfXrdYjNHtAIYGFVwwAEvR1dfxdjKxVzAP0twAAW/ir2w3nzTd3W4yQWO3t0DfleB4XYnEHCEhffdKgaA29p0eo4fHLng9qoG+OVyXz0gMeWGY7qq3xhiRIEAwayNxBawxy777LTXbjsVXRSh++689+7778AHL/zwxBdv/PEnhAAAIfkECQoAAAAsAAAAANwAEwAABf8gII5kaZ5oqq5s675wLM90bd94ru987//AoHBIBAgGhKRyyWw6n9CodEqtWq/YrHY6ELQEhYLD4BlwHGg0ubBpuzdm9Dk9eCTu+MTZkDb4PXYbeIIcHHxqf4F3gnqGY2kOdQmCjHCGfpCSjHhmh2N+knmEkJmKg3uHfgaaeY2qn6t2i4t7sKAPbwIJD2VhXisDCQZgDrKDBQ8aGgjKyhvDlJMJyAjV1gjCunkP1NfVwpRtk93e2ZVt5NfCk27jD97f0LPP7/Dr4pTp1veLgvrx7AL+Q/BM25uBegoYkDCABYFhEobhkUBRwoMGEDJqXPDgQMUEFC9c1LjxQUUJICX/iMRIEgIDkycrjmzJMSXFlDNJvkwJsmdOjQwKfDz5M+PLoSGLQqgZU6XSoB/voHxawGbFlS2XGktAwKEADB0xiEWAodqGBRPSqp1wx5qCamDRrp2Qoa3bagLkzrULF4GCvHPTglRAmKxZvWsHayBcliDitHUlvGWM97FgCdYWVw4c2e/kw4HZJlCwmDBhwHPrjraGYTHqtaoxVKggoesKAgd2SX5rbUMFCxOAC8cGDwHFwBYWJCgu4XfwtcqZV0grPHj0u2SnqwU+IXph3rK5b1fOu7Bx5+K7L6/2/Xhg8uyXnQ8dvfRiDe7TwyfNuzlybKYpgIFtKhAgwEKkKcOf/wChZbBBgMucRh1so5XH3wbI1WXafRJy9iCErmX4IWHNaIAhZ6uxBxeGHXQA24P3yYfBBhmgSBozESpwongWOBhggn/N1aKG8a1YY2oVAklgCgQUUwGJ8iXAgItrWUARbwpqIOWEal0ZoYJbzmWlZCWSlsAC6VkwZonNbMAAl5cpg+NiZwpnJ0Xylegmlc+tWY1mjnGnZnB4QukMA9UJRxGOf5r4ppqDjjmnfKilh2ejGiyJAgF1XNmYbC2GmhZ5AcJVgajcXecNqM9Rx8B6bingnlotviqdkB3YCg+rtOaapFsUhSrsq6axJ6sEwoZK7I/HWpCsr57FBxJ1w8LqV/81zbkoXK3LfVeNpic0KRQG4NHoIW/XEmZuaiN6tti62/moWbk18uhjqerWS6GFpe2YVotskVssWfBOAHACrZHoWcGQwQhlvmsdXBZ/F9YLMF2jzUuYBP4a7CLCnoEHrgkDSCDAARUILAGaVVqAwQHR8pZXomm9/ONhgjrbgc2lyYxmpIRK9uSNjrXs8gEbTrYyl2ryTJmsLCdKkWzFQl1lWlOXGmifal6p9VnbQfpyY2SZyXKVV7JmZkMrgIFSyrIeUJ2r7YKnXdivUg1kAgdQ8B7IzJjGsd9zKSdwyBL03WpwDGxwuOASEP5vriO2F3nLjQdIrpaRDxqcBdgIHGA74pKrZXiR2ZWuZt49m+o3pKMC3p4Av7SNxBa456777rz37jsVXRQh/PDEF2/88cgnr/zyzDfv/PMnhAAAIfkECQoAAAAsAAAAANwAEwAABf8gII5kaZ5oqq5s675wLM90bd94ru987//AoHBIBAgGhKRyyWw6n9CodEqtWq/YrHY6ELQEhYLDUPAMHGi0weEpbN7wI8cxTzsGj4R+n+DUxwaBeBt7hH1/gYIPhox+Y3Z3iwmGk36BkIN8egOIl3h8hBuOkAaZhQlna4BrpnyWa4mleZOFjrGKcXoFA2ReKwMJBgISDw6abwUPGggazc0bBqG0G8kI1tcIwZp51djW2nC03d7BjG8J49jl4cgP3t/RetLp1+vT6O7v5fKhAvnk0UKFogeP3zmCCIoZkDCABQFhChQYuKBHgkUJkxpA2MhxQYEDFhNcvPBAI8eNCx7/gMQYckPJkxsZPLhIM8FLmDJrYiRp8mTKkCwT8IQJwSPQkENhpgQpEunNkzlpWkwKdSbGihKocowqVSvKWQkIOBSgQOYFDBgQpI0oYMGEt3AzTLKm4BqGtnDjirxW95vbvG/nWlub8G9euRsiqqWLF/AEkRoiprX2wLDeDQgkW9PQGLDgyNc665WguK8C0XAnRY6oGPUEuRLsgk5g+a3cCxUqSBC7gsCBBXcVq6swwULx4hayvctGPK8FCwsSLE9A3Hje6NOrHzeOnW695sffRi/9HfDz7sIVSNB+XXrmugo0rHcM3X388o6jr44ceb51uNjF1xcC8zk3wXiS8aYC/wESaLABBs7ch0ECjr2WAGvLsLZBeHqVFl9kGxooV0T81TVhBo6NiOEyJ4p4IYnNRBQiYCN6x4wCG3ZAY2If8jXjYRcyk2FmG/5nXAY8wqhWAii+1YGOSGLoY4VRfqiAgikwmIeS1gjAgHkWYLQZf9m49V9gDWYWY5nmTYCRM2TS5pxxb8IZGV5nhplmhJyZadxzbrpnZ2d/6rnZgHIid5xIMDaDgJfbLdrgMkKW+Rygz1kEZz1mehabkBpgiQIByVikwGTqVfDkk2/Vxxqiqur4X3fksHccre8xlxerDLiHjQIVUAgXr77yFeyuOvYqXGbMrbrqBMqaFpFFzhL7qv9i1FX7ZLR0LUNdcc4e6Cus263KbV+inkAAHhJg0BeITR6WmHcaxhvXg/AJiKO9R77ILF1FwmVdAu6WBu+ZFua72mkZWMfqBElKu0G8rFZ5n4ATp5jkmvsOq+Nj7u63ZMMPv4bveyYy6fDH+C6brgnACHBABQUrkGirz2FwAHnM4Mmhzq9yijOrOi/MKabH6VwBiYwZdukEQAvILKTWXVq0ZvH5/CfUM7M29Zetthp1eht0eqkFYw8IKXKA6mzXfTeH7fZg9zW0AhgY0TwthUa6Ch9dBeIsbsFrYkRBfgTfiG0FhwMWnbsoq3cABUYOnu/ejU/A6uNeT8u4wMb1WnBCyJJTLjjnr8o3OeJrUcpc5oCiPqAEkz8tXuLkPeDL3Uhs4fvvwAcv/PDEU9FFEcgnr/zyzDfv/PPQRy/99NRXf0IIACH5BAkKAAAALAAAAADcABMAAAX/ICCOZGmeaKqubOu+cCzPdG3feK7vfO//wKBwSAQIBoSkcslsOp/QqHRKrVqv2Kx2OhC0BIWCw/AoDziOtCHt8BQ28PjmzK57Hom8fo42+P8DeAkbeYQcfX9+gYOFg4d1bIGEjQmPbICClI9/YwaLjHAJdJeKmZOViGtpn3qOqZineoeJgG8CeWUbBV4rAwkGAhIVGL97hGACGsrKCAgbBoTRhLvN1c3PepnU1s2/oZO6AtzdBoPf4eMI3tIJyOnF0YwFD+nY8e3z7+Xfefnj9uz8cVsXCh89axgk7BrAggAwBQsYIChwQILFixIeNIDAseOCBwcSXMy2sSPHjxJE/6a0eEGjSY4MQGK86PIlypUJEmYsaTKmyJ8JW/Ls6HMkzaEn8YwMWtPkx4pGd76E4DMPRqFTY860OGhogwYagBFoKEABA46DEGBAoEBB0AUT4sqdIFKBNbcC4M6dkEEk22oYFOTdG9fvWrtsBxM23MytYL17666t9phwXwlum2lIDHmuSA2IGyuOLOHv38qLMbdFjHruZbWgRXeOe1nC2BUEDiyAMMHZuwoTLAQX3nvDOAUW5Vogru434d4JnAsnPmFB9NBshQXfa9104+Rxl8e13rZxN+CEydtVsFkd+vDjE7C/q52wOvb4s7+faz025frbxefWbSoQIAEDEUCwgf9j7bUlwHN9ZVaegxDK1xYzFMJH24L5saXABhlYxiEzHoKoIV8LYqAMaw9aZqFmJUK4YHuNfRjiXhmk+NcyJgaIolvM8BhiBx3IleN8lH1IWAcRgkZgCgYiaBGJojGgHHFTgtagAFYSZhF7/qnTpY+faVlNAnqJN0EHWa6ozAZjBtgmmBokwMB01LW5jAZwbqfmlNips4B4eOqJgDJ2+imXRZpthuigeC6XZTWIxilXmRo8iYKBCwiWmWkJVEAkfB0w8KI1IvlIpKnOkVpqdB5+h96o8d3lFnijrgprjbfGRSt0lH0nAZG5vsprWxYRW6Suq4UWqrLEsspWg8Io6yv/q6EhK0Fw0GLbjKYn5CZYBYht1laPrnEY67kyrhYbuyceiR28Pso7bYwiXjihjWsWuWF5p/H765HmNoiur3RJsGKNG/jq748XMrwmjhwCfO6QD9v7LQsDxPTAMKsFpthyJCdkmgYiw0VdXF/Om9dyv7YMWGXTLYpZg5wNR11C78oW3p8HSGgul4qyrJppgllJHJZHn0Y0yUwDXCXUNquFZNLKyYXBAVZvxtAKYIQEsmPgDacr0tltO1y/DMwYpkgUpJfTasLGzd3cdCN3gN3UWRcY3epIEPevfq+3njBxq/kqBoGBduvea8f393zICS63ivRBTqgFpgaWZEIUULdcK+frIfAAL2AjscXqrLfu+uuwx05FF0XUbvvtuOeu++689+7778AHL/wJIQAAOwAAAAAAAAAAAA==") ================================================ FILE: dirigible/shared/static/ace/mode-python.js ================================================ define("ace/mode/python",function(a,b,c){var d=a("pilot/oop"),e=a("ace/mode/text").Mode,f=a("ace/tokenizer").Tokenizer,g=a("ace/mode/python_highlight_rules").PythonHighlightRules,h=a("ace/mode/matching_brace_outdent").MatchingBraceOutdent,i=a("ace/range").Range,j=function(){this.$tokenizer=new f((new g).getRules()),this.$outdent=new h};d.inherits(j,e),function(){this.toggleCommentLines=function(a,b,c,d){var e=!0,f=[],g=/^(\s*)#/;for(var h=c;h<=d;h++)if(!g.test(b.getLine(h))){e=!1;break}if(e){var j=new i(0,0,0,0);for(var h=c;h<=d;h++){var k=b.getLine(h),l=k.match(g);j.start.row=h,j.end.row=h,j.end.column=l[0].length,b.replace(j,l[1])}}else b.indentRows(c,d,"#")},this.getNextLineIndent=function(a,b,c){var d=this.$getIndent(b),e=this.$tokenizer.getLineTokens(b,a),f=e.tokens,g=e.state;if(f.length&&f[f.length-1].type=="comment")return d;if(a=="start"){var h=b.match(/^.*[\{\(\[\:]\s*$/);h&&(d+=c)}return d},this.checkOutdent=function(a,b,c){return this.$outdent.checkOutdent(b,c)},this.autoOutdent=function(a,b,c){this.$outdent.autoOutdent(b,c)}}.call(j.prototype),b.Mode=j}),define("ace/mode/python_highlight_rules",function(a,b,c){var d=a("pilot/oop"),e=a("pilot/lang"),f=a("ace/mode/text_highlight_rules").TextHighlightRules,g=function(){var a=e.arrayToMap("and|as|assert|break|class|continue|def|del|elif|else|except|exec|finally|for|from|global|if|import|in|is|lambda|not|or|pass|print|raise|return|try|while|with|yield".split("|")),b=e.arrayToMap("True|False|None|NotImplemented|Ellipsis|__debug__".split("|")),c=e.arrayToMap("abs|divmod|input|open|staticmethod|all|enumerate|int|ord|str|any|eval|isinstance|pow|sum|basestring|execfile|issubclass|print|super|binfile|iter|property|tuple|bool|filter|len|range|type|bytearray|float|list|raw_input|unichr|callable|format|locals|reduce|unicode|chr|frozenset|long|reload|vars|classmethod|getattr|map|repr|xrange|cmp|globals|max|reversed|zip|compile|hasattr|memoryview|round|__import__|complex|hash|min|set|apply|delattr|help|next|setattr|buffer|dict|hex|object|slice|coerce|dir|id|oct|sorted|intern".split("|")),d=e.arrayToMap("".split("|")),f="(?:r|u|ur|R|U|UR|Ur|uR)?",g="(?:(?:[1-9]\\d*)|(?:0))",h="(?:0[oO]?[0-7]+)",i="(?:0[xX][\\dA-Fa-f]+)",j="(?:0[bB][01]+)",k="(?:"+g+"|"+h+"|"+i+"|"+j+")",l="(?:[eE][+-]?\\d+)",m="(?:\\.\\d+)",n="(?:\\d+)",o="(?:(?:"+n+"?"+m+")|(?:"+n+"\\.))",p="(?:(?:"+o+"|"+n+")"+l+")",q="(?:"+p+"|"+o+")";this.$rules={start:[{token:"comment",regex:"#.*$"},{token:"string",regex:f+'"{3}(?:[^\\\\]|\\\\.)*?"{3}'},{token:"string",regex:f+'"{3}.*$',next:"qqstring"},{token:"string",regex:f+'"(?:[^\\\\]|\\\\.)*?"'},{token:"string",regex:f+"'{3}(?:[^\\\\]|\\\\.)*?'{3}"},{token:"string",regex:f+"'{3}.*$",next:"qstring"},{token:"string",regex:f+"'(?:[^\\\\]|\\\\.)*?'"},{token:"constant.numeric",regex:"(?:"+q+"|\\d+)[jJ]\\b"},{token:"constant.numeric",regex:q},{token:"constant.numeric",regex:k+"[lL]\\b"},{token:"constant.numeric",regex:k+"\\b"},{token:function(e){return a.hasOwnProperty(e)?"keyword":b.hasOwnProperty(e)?"constant.language":d.hasOwnProperty(e)?"invalid.illegal":c.hasOwnProperty(e)?"support.function":e=="debugger"?"invalid.deprecated":"identifier"},regex:"[a-zA-Z_$][a-zA-Z0-9_$]*\\b"},{token:"keyword.operator",regex:"\\+|\\-|\\*|\\*\\*|\\/|\\/\\/|%|<<|>>|&|\\||\\^|~|<|>|<=|=>|==|!=|<>|="},{token:"lparen",regex:"[\\[\\(\\{]"},{token:"rparen",regex:"[\\]\\)\\}]"},{token:"text",regex:"\\s+"}],qqstring:[{token:"string",regex:'(?:[^\\\\]|\\\\.)*?"{3}',next:"start"},{token:"string",regex:".+"}],qstring:[{token:"string",regex:"(?:[^\\\\]|\\\\.)*?'{3}",next:"start"},{token:"string",regex:".+"}]}};d.inherits(g,f),b.PythonHighlightRules=g}) ================================================ FILE: dirigible/shared/static/ace/worker-javascript.js ================================================ function initSender(){var a=require("pilot/event_emitter").EventEmitter,b=require("pilot/oop"),c=function(){};(function(){b.implement(this,a),this.callback=function(a,b){postMessage({type:"call",id:b,data:a})},this.emit=function(a,b){postMessage({type:"event",name:a,data:b})}}).call(c.prototype);return new c}function initBaseUrls(a){require.tlns=a}var console={log:function(a){postMessage({type:"log",data:a})}},window={console:console},require=function(a){var b=require.modules[a];if(b){b.initialized||(b.exports=b.factory().exports,b.initialized=!0);return b.exports}var c=a.split("/");c[0]=require.tlns[c[0]]||c[0],path=c.join("/")+".js",require.id=a,importScripts(path);return require(a)};require.modules={},require.tlns={};var define=function(a,b,c){arguments.length==2?c=b:arguments.length==1&&(c=a,a=require.id);a.indexOf("text/")!==0&&(require.modules[a]={factory:function(){var a={exports:{}},b=c(require,a.exports,a);b&&(a.exports=exports);return a}})},main,sender;onmessage=function(a){var b=a.data;if(b.command)main[b.command].apply(main,b.args);else if(b.init){initBaseUrls(b.tlns),require("pilot/fixoldbrowsers"),sender=initSender();var c=require(b.module)[b.classname];main=new c(sender)}else b.event&&sender._dispatchEvent(b.event,b.data)},define("pilot/fixoldbrowsers",function(a,b,c){var d=Object.prototype.hasOwnProperty;Array.isArray||(Array.isArray=function(a){return Object.prototype.toString.call(a)=="[object Array]"}),Array.prototype.forEach||(Array.prototype.forEach=function(a,b){var c=+this.length;for(var d=0;d=2)var d=arguments[1];else do{if(c in this){d=this[c++];break}if(++c>=b)throw new TypeError}while(!0);for(;c=2)var d=arguments[1];else do{if(c in this){d=this[c--];break}if(--c<0)throw new TypeError}while(!0);for(;c>=0;c--)c in this&&(d=a.call(null,d,this[c],c,this));return d}),Array.prototype.indexOf||(Array.prototype.indexOf=function(a){var b=this.length;if(!b)return-1;var c=arguments[1]||0;if(c>=b)return-1;c<0&&(c+=b);for(;c=0;c--){if(!d.call(this,c))continue;if(a===this[c])return c}return-1}),Object.getPrototypeOf||(Object.getPrototypeOf=function(a){return a.__proto__||a.constructor.prototype}),Object.getOwnPropertyDescriptor||(Object.getOwnPropertyDescriptor=function(a,b){if(typeof a!=="object"&&typeof a!=="function"||a===null)throw new TypeError("Object.getOwnPropertyDescriptor called on a non-object");return d.call(a,b)?{value:a[b],enumerable:!0,configurable:!0,writeable:!0}:undefined}),Object.getOwnPropertyNames||(Object.getOwnPropertyNames=function(a){return Object.keys(a)}),Object.create||(Object.create=function(a,b){var c;if(a===null)c={"__proto__":null};else{if(typeof a!="object")throw new TypeError("typeof prototype["+typeof a+"] != 'object'");var d=function(){};d.prototype=a,c=new d}typeof b!=="undefined"&&Object.defineProperties(c,b);return c}),Object.defineProperty||(Object.defineProperty=function(a,b,c){if(typeof c=="object"&&a.__defineGetter__){if(d.call(c,"value")){!a.__lookupGetter__(b)&&!a.__lookupSetter__(b)&&(a[b]=c.value);if(d.call(c,"get")||d.call(c,"set"))throw new TypeError("Object doesn't support this action")}else typeof c.get=="function"&&a.__defineGetter__(b,c.get);typeof c.set=="function"&&a.__defineSetter__(b,c.set)}return a}),Object.defineProperties||(Object.defineProperties=function(a,b){for(var c in b)d.call(b,c)&&Object.defineProperty(a,c,b[c]);return a}),Object.seal||(Object.seal=function(a){return a}),Object.freeze||(Object.freeze=function(a){return a});try{Object.freeze(function(){})}catch(e){Object.freeze=function(a){return function(b){return typeof b=="function"?b:a(b)}}(Object.freeze)}Object.preventExtensions||(Object.preventExtensions=function(a){return a}),Object.isSealed||(Object.isSealed=function(a){return!1}),Object.isFrozen||(Object.isFrozen=function(a){return!1}),Object.isExtensible||(Object.isExtensible=function(a){return!0});if(!Object.keys){var f=!0,g=["toString","toLocaleString","valueOf","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","constructor"],h=g.length;for(var i in {toString:null})f=!1;Object.keys=function(a){if(typeof a!=="object"&&typeof a!=="function"||a===null)throw new TypeError("Object.keys called on a non-object");var b=[];for(var c in a)d.call(a,c)&&b.push(c);if(f)for(var e=0,i=h;e=7?new a(c,d,e,f,g,h,i):j>=6?new a(c,d,e,f,g,h):j>=5?new a(c,d,e,f,g):j>=4?new a(c,d,e,f):j>=3?new a(c,d,e):j>=2?new a(c,d):j>=1?new a(c):new a;k.constructor=b;return k}return a.apply(this,arguments)},c=new RegExp("^(?:((?:[+-]\\d\\d)?\\d\\d\\d\\d)(?:-(\\d\\d)(?:-(\\d\\d))?)?)?(?:T(\\d\\d):(\\d\\d)(?::(\\d\\d)(?:\\.(\\d\\d\\d))?)?)?(?:Z|([+-])(\\d\\d):(\\d\\d))?$");for(var d in a)b[d]=a[d];b.now=a.now,b.UTC=a.UTC,b.prototype=a.prototype,b.prototype.constructor=b,b.parse=function(b){var d=c.exec(b);if(d){d.shift();var e=d[0]===undefined;for(var f=0;f<10;f++){if(f===7)continue;d[f]=+(d[f]||(f<3?1:0)),f===1&&d[f]--}if(e)return((d[3]*60+d[4])*60+d[5])*1e3+d[6];var g=(d[8]*60+d[9])*60*1e3;d[6]==="-"&&(g=-g);return a.UTC.apply(this,d.slice(0,7))+g}return a.parse.apply(this,arguments)};return b}(Date));var j=Array.prototype.slice;Function.prototype.bind||(Function.prototype.bind=function(a){var b=this;if(typeof b.apply!="function"||typeof b.call!="function")return new TypeError;var c=j.call(arguments),d=function(){if(this instanceof d){var a=Object.create(b.prototype);b.apply(a,c.concat(j.call(arguments)));return a}return b.call.apply(b,c.concat(j.call(arguments)))};d.bound=b,d.boundTo=a,d.boundArgs=c,d.length=typeof b=="function"?Math.max(b.length-c.length,0):0;return d});if(!String.prototype.trim){var k=/^\s\s*/,l=/\s\s*$/;String.prototype.trim=function(){return String(this).replace(k,"").replace(l,"")}}b.globalsLoaded=!0}),define("pilot/event_emitter",function(a,b,c){var d={};d._emit=d._dispatchEvent=function(a,b){this._eventRegistry=this._eventRegistry||{};var c=this._eventRegistry[a];if(!!c&&!!c.length){var b=b||{};b.type=a;for(var d=0;d=b&&(a.row=Math.max(0,b-1),a.column=this.getLine(b-1).length);return a},this.insert=function(a,b){if(b.length==0)return a;a=this.$clipPosition(a),this.getLength()<=1&&this.$detectNewLine(b);var c=this.$split(b),d=c.splice(0,1)[0],e=c.length==0?null:c.splice(c.length-1,1)[0];a=this.insertInLine(a,d),e!==null&&(a=this.insertNewLine(a),a=this.insertLines(a.row,c),a=this.insertInLine(a,e||""));return a},this.insertLines=function(a,b){if(b.length==0)return{row:a,column:0};var c=[a,0];c.push.apply(c,b),this.$lines.splice.apply(this.$lines,c);var d=new f(a,0,a+b.length,0),e={action:"insertLines",range:d,lines:b};this._dispatchEvent("change",{data:e});return d.end},this.insertNewLine=function(a){a=this.$clipPosition(a);var b=this.$lines[a.row]||"";this.$lines[a.row]=b.substring(0,a.column),this.$lines.splice(a.row+1,0,b.substring(a.column,b.length));var c={row:a.row+1,column:0},d={action:"insertText",range:f.fromPoints(a,c),text:this.getNewLineCharacter()};this._dispatchEvent("change",{data:d});return c},this.insertInLine=function(a,b){if(b.length==0)return a;var c=this.$lines[a.row]||"";this.$lines[a.row]=c.substring(0,a.column)+b+c.substring(a.column);var d={row:a.row,column:a.column+b.length},e={action:"insertText",range:f.fromPoints(a,d),text:b};this._dispatchEvent("change",{data:e});return d},this.remove=function(a){a.start=this.$clipPosition(a.start),a.end=this.$clipPosition(a.end);if(a.isEmpty())return a.start;var b=a.start.row,c=a.end.row;if(a.isMultiLine()){var d=a.start.column==0?b:b+1,e=c-1;a.end.column>0&&this.removeInLine(c,0,a.end.column),e>=d&&this.removeLines(d,e),d!=b&&(this.removeInLine(b,a.start.column,this.getLine(b).length),this.removeNewLine(a.start.row))}else this.removeInLine(b,a.start.column,a.end.column);return a.start},this.removeInLine=function(a,b,c){if(b!=c){var d=new f(a,b,a,c),e=this.getLine(a),g=e.substring(b,c),h=e.substring(0,b)+e.substring(c,e.length);this.$lines.splice(a,1,h);var i={action:"removeText",range:d,text:g};this._dispatchEvent("change",{data:i});return d.start}},this.removeLines=function(a,b){var c=new f(a,0,b+1,0),d=this.$lines.splice(a,b-a+1),e={action:"removeLines",range:c,nl:this.getNewLineCharacter(),lines:d};this._dispatchEvent("change",{data:e});return d},this.removeNewLine=function(a){var b=this.getLine(a),c=this.getLine(a+1),d=new f(a,b.length,a+1,0),e=b+c;this.$lines.splice(a,2,e);var g={action:"removeText",range:d,text:this.getNewLineCharacter()};this._dispatchEvent("change",{data:g})},this.replace=function(a,b){if(b.length==0&&a.isEmpty())return a.start;if(b==this.getTextRange(a))return a.end;this.remove(a);if(b)var c=this.insert(a.start,b);else c=a.start;return c},this.applyDeltas=function(a){for(var b=0;b=0;b--){var c=a[b],d=f.fromPoints(c.range.start,c.range.end);c.action=="insertLines"?this.removeLines(d.start.row,d.end.row-1):c.action=="insertText"?this.remove(d):c.action=="removeLines"?this.insertLines(d.start.row,c.lines):c.action=="removeText"&&this.insert(d.start,c.text)}}}).call(h.prototype),b.Document=h}),define("ace/range",function(a,b,c){var d=function(a,b,c,d){this.start={row:a,column:b},this.end={row:c,column:d}};(function(){this.toString=function(){return"Range: ["+this.start.row+"/"+this.start.column+"] -> ["+this.end.row+"/"+this.end.column+"]"},this.contains=function(a,b){return this.compare(a,b)==0},this.compare=function(a,b){if(!this.isMultiLine()&&a===this.start.row)return bthis.end.column?1:0;if(athis.end.row)return 1;if(this.start.row===a)return b>=this.start.column?0:-1;if(this.end.row===a)return b<=this.end.column?0:1;return 0},this.clipRows=function(a,b){if(this.end.row>b)var c={row:b+1,column:0};if(this.start.row>b)var e={row:b+1,column:0};if(this.start.rowthis.row)return;if(c.start.row==this.row&&c.start.column>this.column)return;var d=this.row,e=this.column;b.action==="insertText"?c.start.row===d&&c.start.column<=e?c.start.row===c.end.row?e+=c.end.column-c.start.column:(e-=c.start.column,d+=c.end.row-c.start.row):c.start.row!==c.end.row&&c.start.row=e?e=c.start.column:e=Math.max(0,e-(c.end.column-c.start.column)):c.start.row!==c.end.row&&c.start.row=this.document.getLength()?(c.row=Math.max(0,this.document.getLength()-1),c.column=this.document.getLine(c.row).length):a<0?(c.row=0,c.column=0):(c.row=a,c.column=Math.min(this.document.getLine(c.row).length,Math.max(0,b))),b<0&&(c.column=0);return c}}).call(f.prototype)}),define("pilot/lang",function(a,b,c){b.stringReverse=function(a){return a.split("").reverse().join("")},b.stringRepeat=function(a,b){return Array(b+1).join(a)},b.copyObject=function(a){var b={};for(var c in a)b[c]=a[c];return b},b.arrayToMap=function(a){var b={};for(var c=0;c'.",f,d),c=g.empty,f.type=d;for(;;){if(K.id==="/"){bI("/"),K.id!==">"&&bA("Expected '{a}' and instead saw '{b}'.",K,">",K.value);break}if(K.id&&K.id.substr(0,1)===">")break;K.identifier||((K.id==="(end)"||K.id==="(error)")&&bC("Missing '>'.",K),bA("Bad identifier.")),N.white=!0,bN($,K),a=K.value,N.white=h,bI(),!N.cap&&a!==a.toLowerCase()&&bA("Attribute '{a}' not all lower case.",K,a),a=a.toLowerCase(),be="",bw(b,a)&&bA("Attribute '{a}' repeated.",K,a),a.slice(0,2)==="on"?(N.on||bA("Avoid HTML event handlers."),bd="scriptstring",bI("="),e=K.id,e!=='"'&&e!=="'"&&bC("Missing quote."),be=e,i=N.white,N.white=!1,bI(e),ck(),cl("on"),N.white=i,K.id!==e&&bC("Missing close quote on script attribute."),bd="html",be="",bI(e),g=!1):a==="style"?(bd="scriptstring",bI("="),e=K.id,e!=='"'&&e!=="'"&&bC("Missing quote."),bd="styleproperty",be=e,bI(e),cF(),bd="html",be="",bI(e),g=!1):K.id==="="?(bI("="),g=K.value,!K.identifier&&K.id!=='"'&&K.id!=="'"&&K.type!=="(string)"&&K.type!=="(number)"&&K.type!=="(color)"&&bA("Expected an attribute value and instead saw '{a}'.",$,a),bI()):g=!0,b[a]=g,cL(d,a,g)}cM(d,b),c||U.push(f),bd="outer",bI(">");break;case""&&bC("Missing '{a}'.",K,">"),bd="outer",bI(">");break;case""||K.id==="(end)")break;K.value.indexOf("--")>=0&&bC("Unexpected --."),K.value.indexOf("<")>=0&&bC("Unexpected <."),K.value.indexOf(">")>=0&&bC("Unexpected >.")}bd="outer",bI(">");break;case"(end)":return;default:K.id==="(end)"?bC("Missing '{a}'.",K,""):bI()}if(U&&U.length===0&&(N.adsafe||!N.fragment||K.id==="(end)"))break}K.id!=="(end)"&&bC("Unexpected material after the end.")}function cN(a){return""}function cM(d,e){var g,h=z[d],i;T=!1,h||bC("Unrecognized tag '<{a}>'.",K,d===d.toLowerCase()?d:d+" (capitalization error)");if(U.length>0){d==="html"&&bC("Too many tags.",$),i=h.parent;if(i)i.indexOf(" "+U[U.length-1].name+" ")<0&&bC("A '<{a}>' must be within '<{b}>'.",$,d,i);else if(!N.adsafe&&!N.fragment){g=U.length;do g<=0&&bC("A '<{a}>' must be within '<{b}>'.",$,d,"body"),g-=1;while(U[g].name!=="body")}}switch(d){case"div":N.adsafe&&U.length===1&&!a&&bA("ADSAFE violation: missing ID_.");break;case"script":bd="script",bI(">"),D=K.from,e.lang&&bA("lang is deprecated.",$),N.adsafe&&U.length!==1&&bA("ADsafe script placement violation.",$),e.src?(N.adsafe&&(!b||!f[e.src])&&bA("ADsafe unapproved script source.",$),e.type&&bA("type is unnecessary.",$)):(c&&bC("ADsafe script violation.",$),ck(),cl("script")),bd="html",bI(""),cJ(),bd="html",bI("=0&&bA("Unexpected character '{a}' in {b}.",$,d.charAt(f),c),A[e]=!0):c==="class"||c==="type"||c==="name"?(f=d.search(bs),f>=0&&bA("Unexpected character '{a}' in {b}.",$,d.charAt(f),c),A[e]=!0):c==="href"||c==="background"||c==="content"||c==="data"||c.indexOf("src")>=0||c.indexOf("url")>=0?(N.safe&&bp.test(d)&&bC("ADsafe URL violation."),_.push(d)):c==="for"?N.adsafe&&(a?d.slice(0,a.length)!==a?bA("ADsafe violation: An id must have a '{a}' prefix",K,a):/^[A-Z]+_[A-Z]+$/.test(d)||bA("ADSAFE violation: bad id."):bA("ADSAFE violation: bad id.")):c==="name"&&N.adsafe&&d.indexOf("_")>=0&&bA("ADsafe name violation.")}function cK(a){a!=="html"&&!N.fragment&&(a==="div"&&N.adsafe?bC("ADSAFE: Use the fragment option."):bC("Expected '{a}' and instead saw '{b}'.",$,"html",a)),N.adsafe&&(a==="html"&&bC("Currently, ADsafe does not operate on whole HTML documents. It operates on
    fragments and .js files.",$),N.fragment?a!=="div"&&bC("ADsafe violation: Wrap the widget in a div.",$):bC("Use the fragment option.",$)),N.browser=!0,by()}function cJ(){var a;while(K.id==="@"){a=bH(),bI("@");if(K.identifier)switch(K.value){case"import":bI(),cB()||(bA("Expected '{a}' and instead saw '{b}'.",K,"url",K.value),bI()),bI(";");break;case"media":bI();for(;;){(!K.identifier||r[K.value]===!0)&&bC("Expected a CSS media type, and instead saw '{a}'.",K,K.id),bI();if(K.id!==",")break;bI(",")}bI("{"),cI(),bI("}");break;default:bA("Expected an at-rule, and instead saw @{a}.",K,K.value)}else bA("Expected an at-rule, and instead saw '{a}'.",K,K.value)}cI()}function cI(){while(K.id!=="":case"+":bI(),cG();break;case":":bI(":");switch(K.value){case"active":case"after":case"before":case"checked":case"disabled":case"empty":case"enabled":case"first-child":case"first-letter":case"first-line":case"first-of-type":case"focus":case"hover":case"last-child":case"last-of-type":case"link":case"only-of-type":case"root":case"target":case"visited":bI();break;case"lang":bI(),bI("("),K.identifier||bA("Expected a lang code, and instead saw :{a}.",K,K.value),bI(")");break;case"nth-child":case"nth-last-child":case"nth-last-of-type":case"nth-of-type":bI(),bI("("),cE(),bI(")");break;case"not":bI(),bI("("),K.id===":"&&bH(0).value==="not"&&bA("Nested not."),cG(),bI(")");break;default:bA("Expected a pseudo, and instead saw :{a}.",K,K.value)}break;case"#":bI("#"),K.identifier||bA("Expected an id, and instead saw #{a}.",K,K.value),bI();break;case"*":bI("*");break;case".":bI("."),K.identifier||bA("Expected a class, and instead saw #.{a}.",K,K.value),bI();break;case"[":bI("["),K.identifier||bA("Expected an attribute, and instead saw [{a}].",K,K.value),bI();if(K.id==="="||K.value==="~="||K.value==="$="||K.value==="|="||K.id==="*="||K.id==="^=")bI(),K.type!=="(string)"&&bA("Expected a string, and instead saw {a}.",K,K.value),bI();bI("]");break;default:bC("Expected a CSS selector, and instead saw {a}.",K,K.value)}}function cF(){var a;for(;;){if(K.id==="}"||K.id==="(end)"||be&&K.id===be)return;while(K.id===";")bA("Misplaced ';'."),bI(";");a=cC(),bI(":"),K.identifier&&K.value==="inherit"?bI():cD(a)||(bA("Unexpected token '{a}'.",K,K.value),bI()),K.id==="!"&&(bI("!"),bK(),K.identifier&&K.value==="important"?bI():bA("Expected '{a}' and instead saw '{b}'.",K,"important",K.value)),K.id==="}"||K.id===be?bA("Missing '{a}'.",K,";"):bI(";")}}function cE(){if(K.id==="(number)")bI(),K.value==="n"&&K.identifier&&(bK(),bI(),K.id==="+"&&(bK(),bI("+"),bK(),bI("(number)")));else{switch(K.value){case"odd":case"even":if(K.identifier){bI();return}}bA("Unexpected token '{a}'.",K,K.value)}}function cD(a){var b=0,c,d,e,f,g=0,h;switch(typeof a){case"function":return a();case"string":if(K.identifier&&K.value===a){bI();return!0}return!1}for(;;){if(b>=a.length)return!1;h=a[b],b+=1;if(h===!0)break;typeof h==="number"?(c=h,h=a[b],b+=1):c=1,e=!1;while(c>0)if(cD(h))e=!0,c-=1;else break;if(e)return!0}g=b,d=[];for(;;){f=!1;for(b=g;b=0&&bA("Bad url string."));b||bA("Missing url."),bI(),N.safe&&bp.test(b)&&bC("ADsafe URL violation."),_.push(b);return!0}return!1}function cA(){var a;if(K.identifier&&K.value==="rect"){bI(),bI("(");for(a=0;a<4;a+=1)if(!ct()){bA("Expected a number and instead saw '{a}'.",K,K.value);break}bI(")");return!0}return!1}function cz(){if(K.identifier&&K.value==="counter"){bI(),bI("("),bI(),K.id===","&&(bR(),K.type!=="(string)"&&bA("Expected a string and instead saw '{a}'.",K,K.value),bI()),bI(")");return!0}if(K.identifier&&K.value==="counters"){bI(),bI("("),K.identifier||bA("Expected a name and instead saw '{a}'.",K,K.value),bI(),K.id===","&&(bR(),K.type!=="(string)"&&bA("Expected a string and instead saw '{a}'.",K,K.value),bI()),K.id===","&&(bR(),K.type!=="(string)"&&bA("Expected a string and instead saw '{a}'.",K,K.value),bI()),bI(")");return!0}return!1}function cy(){while(K.id!==";"){!cp()&&!cr()&&bA("Expected a name and instead saw '{a}'.",K,K.value);if(K.id!==",")return!0;bR()}}function cx(){if(K.identifier&&K.value==="attr"){bI(),bI("("),K.identifier||bA("Expected a name and instead saw '{a}'.",K,K.value),bI(),bI(")");return!0}return!1}function cw(){if(!K.identifier)return ct();if(K.value==="auto"){bI();return!0}}function cv(){if(!K.identifier)return ct();switch(K.value){case"thin":case"medium":case"thick":bI();return!0}}function cu(){K.id==="-"&&(bI("-"),bK());if(K.type==="(number)"){bI(),K.type!=="(string)"&&q[K.value]===!0&&(bK(),bI());return!0}return!1}function ct(){K.id==="-"&&(bI("-"),bK(),bQ());if(K.type==="(number)"){bI(),K.type!=="(string)"&&q[K.value]===!0?(bK(),bI()):+$.value!==0&&bA("Expected a linear unit and instead saw '{a}'.",K,K.value);return!0}return!1}function cs(){var a,b,c;if(K.identifier){c=K.value;if(c==="rgb"||c==="rgba"){bI(),bI("(");for(a=0;a<3;a+=1)a&&bI(","),b=K.value,K.type!=="(number)"||b<0?(bA("Expected a positive number and instead saw '{a}'",K,b),bI()):(bI(),K.id==="%"?(bI("%"),b>100&&bA("Expected a percentage and instead saw '{a}'",$,b)):b>255&&bA("Expected a small number and instead saw '{a}'",$,b));c==="rgba"&&(bI(","),b=+K.value,(K.type!=="(number)"||b<0||b>1)&&bA("Expected a number between 0 and 1 and instead saw '{a}'",K,b),bI(),K.id==="%"&&(bA("Unexpected '%'."),bI("%"))),bI(")");return!0}if(n[K.value]===!0){bI();return!0}}else if(K.type==="(color)"){bI();return!0}return!1}function cr(){if(K.type==="(string)"){bI();return!0}}function cq(){K.id==="-"&&(bI("-"),bK(),bQ());if(K.type==="(number)"){bI("(number)");return!0}}function cp(){if(K.identifier){bI();return!0}}function co(a){var b=a.value,c=a.line,d=B[b];typeof d==="function"&&(d=!1),d?d[d.length-1]!==c&&d.push(c):(d=[c],B[b]=d)}function cn(a){J&&typeof J[a]!=="boolean"&&bA("Unexpected /*member '{a}'.",$,a),typeof I[a]==="number"?I[a]+=1:I[a]=1}function cm(a,b){var c,d=C,e=D,f=X,g=S,h;C=a,S=Object.create(S),bN($,K),h=K;if(K.id==="{"){bI("{");if(K.id!=="}"||$.line!==K.line){D+=N.indent;while(!a&&K.from>D)D+=N.indent;!a&&!ck()&&!f&&N.strict&&v["(context)"]["(global)"]&&bA('Missing "use strict" statement.'),c=cl(),X=f,D-=N.indent,bP()}bI("}",h),D=e}else a?((!b||N.curly)&&bA("Expected '{a}' and instead saw '{b}'.",K,"{",K.value),M=!0,c=[cj()],M=!1):bC("Expected '{a}' and instead saw '{b}'.",K,"{",K.value);v["(verb)"]=null,S=g,C=d,a&&N.noempty&&(!c||c.length===0)&&bA("Empty block.");return c}function cl(c){var d=[],e,f;if(N.adsafe)switch(c){case"script":b||(K.value!=="ADSAFE"||bH(0).id!=="."||bH(1).value!=="id"&&bH(1).value!=="go")&&bC("ADsafe violation: Missing ADSAFE.id or ADSAFE.go.",K),K.value==="ADSAFE"&&bH(0).id==="."&&bH(1).value==="id"&&(b&&bC("ADsafe violation.",K),bI("ADSAFE"),bI("."),bI("id"),bI("("),K.value!==a&&bC("ADsafe violation: id does not match.",K),bI("(string)"),bI(")"),bI(";"),b=!0);break;case"lib":if(K.value==="ADSAFE"){bI("ADSAFE"),bI("."),bI("lib"),bI("("),bI("(string)"),bR(),e=bJ(0),e.id!=="function"&&bC("The second argument to lib must be a function.",e),f=e.funct["(params)"],f=f&&f.join(", "),f&&f!=="lib"&&bC("Expected '{a}' and instead saw '{b}'.",e,"(lib)","("+f+")"),bI(")"),bI(";");return d}bC("ADsafe lib violation.")}while(!K.reach&&K.id!=="(end)")K.id===";"?(bA("Unnecessary semicolon."),bI(";")):d.push(cj());return d}function ck(){if(K.value==="use strict"){X&&bA('Unnecessary "use strict".'),bI(),bI(";"),X=!0,N.newcap=!0,N.undef=!0;return!0}return!1}function cj(a){var b=D,c,d=S,e=K;if(e.id===";")bA("Unnecessary semicolon.",e),bI(";");else{e.identifier&&!e.reserved&&bH().id===":"&&(bI(),bI(":"),S=Object.create(d),bF(e.value,"label"),K.labelled||bA("Label '{a}' on {b} statement.",K,e.value,K.value),bo.test(e.value+":")&&bA("Label '{a}' looks like a javascript url.",e,e.value),K.label=e.value,e=K),a||bP(),c=bJ(0,!0),e.block||(!c||!c.exps?bA("Expected an assignment or function call and instead saw an expression.",$):N.nonew&&c.id==="("&&c.left.id==="new"&&bA("Do not use 'new' for side effects."),K.id!==";"?bB("Missing semicolon.",$.line,$.from+$.value.length):(bK($,K),bI(";"),bN($,K))),D=b,S=d;return c}}function ci(a){var b=0,c;if(K.id===";"&&!M)for(;;){c=bH(b);if(c.reach)return;if(c.id!=="(endline)"){if(c.id==="function"){bA("Inner functions should be listed at the top of the outer function.",c);break}bA("Unreachable '{a}' after '{b}'.",c,c.value,a);break}b+=1}}function ch(a){var b=cg(a);if(b)return b;$.id==="function"&&K.id==="("?bA("Missing name in function statement."):bC("Expected an identifier and instead saw '{a}'.",K,K.value)}function cg(a){if(K.identifier){bI(),N.safe&&h[$.value]?bA("ADsafe violation: '{a}'.",$,$.value):$.reserved&&!N.es5&&(!a||$.value!="undefined")&&bA("Expected an identifier and instead saw '{a}' (a reserved word).",$,$.id);return $.value}}function cf(a,b){var c=bS(a,150);c.led=function(a){N.plusplus?bA("Unexpected use of '{a}'.",this,this.id):(!a.identifier||a.reserved)&&a.id!=="."&&a.id!=="["&&bA("Bad operand.",this),this.left=a;return this};return c}function ce(a){bS(a,20).exps=!0;return b_(a,function(a,b){N.bitwise&&bA("Unexpected use of '{a}'.",b,b.id),bN(Q,$),bN($,K);if(a){if(a.id==="."||a.id==="["||a.identifier&&!a.reserved){bJ(19);return b}a===Y["function"]&&bA("Expected an identifier in an assignment, and instead saw a function invocation.",$);return b}bC("Bad assignment.",b)},20)}function cd(a,b,c){var d=bS(a,c);bW(d),d.led=typeof b==="function"?b:function(a){N.bitwise&&bA("Unexpected use of '{a}'.",this,this.id),this.left=a,this.right=bJ(c);return this};return d}function cc(a,b){bS(a,20).exps=!0;return b_(a,function(a,b){var c;b.left=a,O[a.value]===!1&&S[a.value]["(global)"]===!0?bA("Read only.",a):a["function"]&&bA("'{a}' is a function.",a,a.value);if(N.safe){c=a;do typeof O[c.value]==="boolean"&&bA("ADsafe violation.",c),c=c.left;while(c)}if(a){if(a.id==="."||a.id==="["){(!a.left||a.left.value==="arguments")&&bA("Bad assignment.",b),b.right=bJ(19);return b}if(a.identifier&&!a.reserved){v[a.value]==="exception"&&bA("Do not assign to the exception parameter.",a),b.right=bJ(19);return b}a===Y["function"]&&bA("Expected an identifier in an assignment and instead saw a function invocation.",$)}bC("Bad assignment.",b)},20)}function cb(a){return a&&(a.type==="(number)"&&+a.value===0||a.type==="(string)"&&a.value===""||a.type==="null"&&!N.boss||a.type==="true"||a.type==="false"||a.type==="undefined")}function ca(a,b){var c=bS(a,100);c.led=function(a){bO(Q,$),bN($,K);var c=bJ(100);a&&a.id==="NaN"||c&&c.id==="NaN"?bA("Use the isNaN function to compare with NaN.",this):b&&b.apply(this,[a,c]),a.id==="!"&&bA("Confusing use of '{a}'.",a,"!"),c.id==="!"&&bA("Confusing use of '{a}'.",a,"!"),this.left=a,this.right=c;return this};return c}function b_(a,b,c,d){var e=bS(a,c);bW(e),e.led=function(a){d||(bO(Q,$),bN($,K));if(typeof b==="function")return b(a,this);this.left=a,this.right=bJ(c);return this};return e}function b$(a,b){return bZ(a,function(){typeof b==="function"&&b(this);return this})}function bZ(a,b){var c=bY(a,b);c.identifier=c.reserved=!0;return c}function bY(a,b){var c=bT(a);c.type=a,c.nud=b;return c}function bX(a,b){var c=bS(a,150);bW(c),c.nud=typeof b==="function"?b:function(){this.right=bJ(150),this.arity="unary";if(this.id==="++"||this.id==="--")N.plusplus?bA("Unexpected use of '{a}'.",this,this.id):(!this.right.identifier||this.right.reserved)&&this.right.id!=="."&&this.right.id!=="["&&bA("Bad operand.",this);return this};return c}function bW(a){var b=a.id.charAt(0);if(b>="a"&&b<="z"||b>="A"&&b<="Z")a.identifier=a.reserved=!0;return a}function bV(a,b){var c=bU(a,b);c.block=!0;return c}function bU(a,b){var c=bT(a);c.identifier=c.reserved=!0,c.fud=b;return c}function bT(a){return bS(a,0)}function bS(a,b){var c=Y[a];if(!c||typeof c!=="object")Y[a]=c={id:a,lbp:b,value:a};return c}function bR(){$.line!==K.line?N.laxbreak||bA("Bad line breaking before '{a}'.",$,K.id):$.character!==K.from&&N.white&&bA("Unexpected space after '{a}'.",K,$.value),bI(","),bN($,K)}function bQ(a){a=a||$,a.line!==K.line&&bA("Line breaking error '{a}'.",a,a.value)}function bP(a){var b;N.white&&K.id!=="(end)"&&(b=D+(a||0),K.from!==b&&bA("Expected '{a}' to have an indentation at {b} instead at {c}.",K,K.value,b,K.from))}function bO(a,b){a=a||$,b=b||K,!N.laxbreak&&a.line!==b.line?bA("Bad line breaking before '{a}'.",b,b.id):N.white&&(a=a||$,b=b||K,a.character===b.from&&bA("Missing space after '{a}'.",K,a.value))}function bN(a,b){N.white&&(a=a||$,b=b||K,a.line===b.line&&a.character===b.from&&bA("Missing space after '{a}'.",K,a.value))}function bM(a,b){a=a||$,b=b||K,N.white&&!a.comment&&a.line===b.line&&bK(a,b)}function bL(a,b){a=a||$,b=b||K,N.white&&(a.character!==b.from||a.line!==b.line)&&bA("Unexpected space before '{a}'.",b,b.value)}function bK(a,b){a=a||$,b=b||K,(N.white||bd==="styleproperty"||bd==="style")&&a.character!==b.from&&a.line===b.line&&bA("Unexpected space after '{a}'.",b,a.value)}function bJ(a,b){var c;K.id==="(end)"&&bC("Unexpected early end of program.",$),bI(),N.safe&&typeof O[$.value]==="boolean"&&K.id!=="("&&K.id!=="."&&bA("ADsafe violation.",$),b&&(e="anonymous",v["(verb)"]=$.value);if(b===!0&&$.fud)c=$.fud();else{if($.nud)c=$.nud();else{if(K.type==="(number)"&&$.id==="."){bA("A leading decimal point can be confused with a dot: '.{a}'.",$,K.value),bI();return $}bC("Expected an identifier and instead saw '{a}'.",$,$.id)}while(a=N.maxerr&&bz("Too many errors.",i,h);return j}function bz(a,b,c){throw{name:"JSHintError",line:b,character:c,message:a+" ("+Math.floor(b/G.length*100)+"% scanned)."}}function by(){N.safe||(N.couch&&bx(O,k),N.rhino&&bx(O,R),N.node&&bx(O,L),N.devel&&bx(O,t),N.browser&&bx(O,j),N.jquery&&bx(O,F),N.windows&&bx(O,bc),N.widget&&bx(O,bb))}function bx(a,b){var c;for(c in b)bw(b,c)&&(a[c]=b[c])}function bw(a,b){return Object.prototype.hasOwnProperty.call(a,b)}function bv(){}"use strict";var a,b,c,e,f,g={"<":!0,"<=":!0,"==":!0,"===":!0,"!==":!0,"!=":!0,">":!0,">=":!0,"+":!0,"-":!0,"*":!0,"/":!0,"%":!0},h={arguments:!0,callee:!0,caller:!0,constructor:!0,eval:!0,prototype:!0,stack:!0,unwatch:!0,valueOf:!0,watch:!0},i={adsafe:!0,bitwise:!0,boss:!0,browser:!0,cap:!0,couch:!0,css:!0,curly:!0,debug:!0,devel:!0,eqeqeq:!0,es5:!0,evil:!0,forin:!0,fragment:!0,immed:!0,jquery:!0,laxbreak:!0,newcap:!0,noarg:!0,node:!0,noempty:!0,nonew:!0,nomen:!0,on:!0,onevar:!0,passfail:!0,plusplus:!0,regexp:!0,rhino:!0,undef:!0,safe:!0,windows:!0,strict:!0,sub:!0,white:!0,widget:!0},j={addEventListener:!1,applicationCache:!1,blur:!1,clearInterval:!1,clearTimeout:!1,close:!1,closed:!1,defaultStatus:!1,document:!1,event:!1,FileReader:!1,focus:!1,frames:!1,getComputedStyle:!1,history:!1,Image:!1,length:!1,localStorage:!1,location:!1,moveBy:!1,moveTo:!1,name:!1,navigator:!1,onbeforeunload:!0,onblur:!0,onerror:!0,onfocus:!0,onload:!0,onresize:!0,onunload:!0,open:!1,openDatabase:!1,opener:!1,Option:!1,parent:!1,print:!1,removeEventListener:!1,resizeBy:!1,resizeTo:!1,screen:!1,scroll:!1,scrollBy:!1,scrollTo:!1,setInterval:!1,setTimeout:!1,status:!1,top:!1,WebSocket:!1,window:!1,Worker:!1,XMLHttpRequest:!1},k={require:!1,respond:!1,getRow:!1,emit:!1,send:!1,start:!1,sum:!1,log:!1,exports:!1,module:!1},l,m,n={aliceblue:!0,antiquewhite:!0,aqua:!0,aquamarine:!0,azure:!0,beige:!0,bisque:!0,black:!0,blanchedalmond:!0,blue:!0,blueviolet:!0,brown:!0,burlywood:!0,cadetblue:!0,chartreuse:!0,chocolate:!0,coral:!0,cornflowerblue:!0,cornsilk:!0,crimson:!0,cyan:!0,darkblue:!0,darkcyan:!0,darkgoldenrod:!0,darkgray:!0,darkgreen:!0,darkkhaki:!0,darkmagenta:!0,darkolivegreen:!0,darkorange:!0,darkorchid:!0,darkred:!0,darksalmon:!0,darkseagreen:!0,darkslateblue:!0,darkslategray:!0,darkturquoise:!0,darkviolet:!0,deeppink:!0,deepskyblue:!0,dimgray:!0,dodgerblue:!0,firebrick:!0,floralwhite:!0,forestgreen:!0,fuchsia:!0,gainsboro:!0,ghostwhite:!0,gold:!0,goldenrod:!0,gray:!0,green:!0,greenyellow:!0,honeydew:!0,hotpink:!0,indianred:!0,indigo:!0,ivory:!0,khaki:!0,lavender:!0,lavenderblush:!0,lawngreen:!0,lemonchiffon:!0,lightblue:!0,lightcoral:!0,lightcyan:!0,lightgoldenrodyellow:!0,lightgreen:!0,lightpink:!0,lightsalmon:!0,lightseagreen:!0,lightskyblue:!0,lightslategray:!0,lightsteelblue:!0,lightyellow:!0,lime:!0,limegreen:!0,linen:!0,magenta:!0,maroon:!0,mediumaquamarine:!0,mediumblue:!0,mediumorchid:!0,mediumpurple:!0,mediumseagreen:!0,mediumslateblue:!0,mediumspringgreen:!0,mediumturquoise:!0,mediumvioletred:!0,midnightblue:!0,mintcream:!0,mistyrose:!0,moccasin:!0,navajowhite:!0,navy:!0,oldlace:!0,olive:!0,olivedrab:!0,orange:!0,orangered:!0,orchid:!0,palegoldenrod:!0,palegreen:!0,paleturquoise:!0,palevioletred:!0,papayawhip:!0,peachpuff:!0,peru:!0,pink:!0,plum:!0,powderblue:!0,purple:!0,red:!0,rosybrown:!0,royalblue:!0,saddlebrown:!0,salmon:!0,sandybrown:!0,seagreen:!0,seashell:!0,sienna:!0,silver:!0,skyblue:!0,slateblue:!0,slategray:!0,snow:!0,springgreen:!0,steelblue:!0,tan:!0,teal:!0,thistle:!0,tomato:!0,turquoise:!0,violet:!0,wheat:!0,white:!0,whitesmoke:!0,yellow:!0,yellowgreen:!0,activeborder:!0,activecaption:!0,appworkspace:!0,background:!0,buttonface:!0,buttonhighlight:!0,buttonshadow:!0,buttontext:!0,captiontext:!0,graytext:!0,highlight:!0,highlighttext:!0,inactiveborder:!0,inactivecaption:!0,inactivecaptiontext:!0,infobackground:!0,infotext:!0,menu:!0,menutext:!0,scrollbar:!0,threeddarkshadow:!0,threedface:!0,threedhighlight:!0,threedlightshadow:!0,threedshadow:!0,window:!0,windowframe:!0,windowtext:!0},o,p,q={"%":!0,cm:!0,em:!0,ex:!0,"in":!0,mm:!0,pc:!0,pt:!0,px:!0},r,s,t={alert:!1,confirm:!1,console:!1,Debug:!1,opera:!1,prompt:!1},u={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"/":"\\/","\\":"\\\\"},v,w=["closure","exception","global","label","outer","unused","var"],x,y,z={a:{},abbr:{},acronym:{},address:{},applet:{},area:{empty:!0,parent:" map "},article:{},aside:{},audio:{},b:{},base:{empty:!0,parent:" head "},bdo:{},big:{},blockquote:{},body:{parent:" html noframes "},br:{empty:!0},button:{},canvas:{parent:" body p div th td "},caption:{parent:" table "},center:{},cite:{},code:{},col:{empty:!0,parent:" table colgroup "},colgroup:{parent:" table "},command:{parent:" menu "},datalist:{},dd:{parent:" dl "},del:{},details:{},dialog:{},dfn:{},dir:{},div:{},dl:{},dt:{parent:" dl "},em:{},embed:{},fieldset:{},figure:{},font:{},footer:{},form:{},frame:{empty:!0,parent:" frameset "},frameset:{parent:" html frameset "},h1:{},h2:{},h3:{},h4:{},h5:{},h6:{},head:{parent:" html "},header:{},hgroup:{},hr:{empty:!0},"hta:application":{empty:!0,parent:" head "},html:{parent:"*"},i:{},iframe:{},img:{empty:!0},input:{empty:!0},ins:{},kbd:{},keygen:{},label:{},legend:{parent:" details fieldset figure "},li:{parent:" dir menu ol ul "},link:{empty:!0,parent:" head "},map:{},mark:{},menu:{},meta:{empty:!0,parent:" head noframes noscript "},meter:{},nav:{},noframes:{parent:" html body "},noscript:{parent:" body head noframes "},object:{},ol:{},optgroup:{parent:" select "},option:{parent:" optgroup select "},output:{},p:{},param:{empty:!0,parent:" applet object "},pre:{},progress:{},q:{},rp:{},rt:{},ruby:{},samp:{},script:{empty:!0,parent:" body div frame head iframe p pre span "},section:{},select:{},small:{},span:{},source:{},strong:{},style:{parent:" head ",empty:!0},sub:{},sup:{},table:{},tbody:{parent:" table "},td:{parent:" tr "},textarea:{},tfoot:{parent:" table "},th:{parent:" tr "},thead:{parent:" table "},time:{},title:{parent:" head "},tr:{parent:" table tbody thead tfoot "},tt:{},u:{},ul:{},"var":{},video:{}},A,B,C,D,E,F={$:!1,jQuery:!1},G,H,I,J,K,L={__filename:!1,__dirname:!1,Buffer:!1,GLOBAL:!1,global:!1,module:!1,process:!1,require:!1},M,N,O,P,Q,R={defineClass:!1,deserialize:!1,gc:!1,help:!1,load:!1,loadClass:!1,print:!1,quit:!1,readFile:!1,readUrl:!1,runCommand:!1,seal:!1,serialize:!1,spawn:!1,sync:!1,toint32:!1,version:!1},S,T,U,V={Array:!1,Boolean:!1,Date:!1,decodeURI:!1,decodeURIComponent:!1,encodeURI:!1,encodeURIComponent:!1,Error:!1,eval:!1,EvalError:!1,Function:!1,hasOwnProperty:!1,isFinite:!1,isNaN:!1,JSON:!1,Math:!1,Number:!1,Object:!1,parseInt:!1,parseFloat:!1,RangeError:!1,ReferenceError:!1,RegExp:!1,String:!1,SyntaxError:!1,TypeError:!1,URIError:!1},W={E:!0,LN2:!0,LN10:!0,LOG2E:!0,LOG10E:!0,MAX_VALUE:!0,MIN_VALUE:!0,NEGATIVE_INFINITY:!0,PI:!0,POSITIVE_INFINITY:!0,SQRT1_2:!0,SQRT2:!0},X,Y={},Z,$,_,ba,bb={alert:!0,animator:!0,appleScript:!0,beep:!0,bytesToUIString:!0,Canvas:!0,chooseColor:!0,chooseFile:!0,chooseFolder:!0,closeWidget:!0,COM:!0,convertPathToHFS:!0,convertPathToPlatform:!0,CustomAnimation:!0,escape:!0,FadeAnimation:!0,filesystem:!0,Flash:!0,focusWidget:!0,form:!0,FormField:!0,Frame:!0,HotKey:!0,Image:!0,include:!0,isApplicationRunning:!0,iTunes:!0,konfabulatorVersion:!0,log:!0,md5:!0,MenuItem:!0,MoveAnimation:!0,openURL:!0,play:!0,Point:!0,popupMenu:!0,preferenceGroups:!0,preferences:!0,print:!0,prompt:!0,random:!0,Rectangle:!0,reloadWidget:!0,ResizeAnimation:!0,resolvePath:!0,resumeUpdates:!0,RotateAnimation:!0,runCommand:!0,runCommandInBg:!0,saveAs:!0,savePreferences:!0,screen:!0,ScrollBar:!0,showWidgetPreferences:!0,sleep:!0,speak:!0,Style:!0,suppressUpdates:!0,system:!0,tellWidget:!0,Text:!0,TextArea:!0,Timer:!0,unescape:!0,updateNow:!0,URL:!0,Web:!0,widget:!0,Window:!0,XMLDOM:!0,XMLHttpRequest:!0,yahooCheckLogin:!0,yahooLogin:!0,yahooLogout:!0},bc={ActiveXObject:!1,CScript:!1,Debug:!1,Enumerator:!1,System:!1,VBArray:!1,WScript:!1},bd,be,bf=/@cc|<\/?|script|\]\s*\]|<\s*!|</i,bg=/[\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/,bh=/^\s*([(){}\[.,:;'"~\?\]#@]|==?=?|\/(\*(jshint|members?|global)?|=|\/)?|\*[\/=]?|\+(?:=|\++)?|-(?:=|-+)?|%=?|&[&=]?|\|[|=]?|>>?>?=?|<([\/=!]|\!(\[|--)?|<=?)?|\^=?|\!=?=?|[a-zA-Z_$][a-zA-Z0-9_$]*|[0-9]+([xX][0-9a-fA-F]+|\.[0-9]*)?([eE][+\-]?[0-9]+)?)/,bi=/^\s*(['"=>\/&#]|<(?:\/|\!(?:--)?)?|[a-zA-Z][a-zA-Z0-9_\-:]*|[0-9]+|--)/,bj=/[\u0000-\u001f&<"\/\\\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/,bk=/[\u0000-\u001f&<"\/\\\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,bl=/[>&]|<[\/!]?|--/,bm=/\*\/|\/\*/,bn=/^([a-zA-Z_$][a-zA-Z0-9_$]*)$/,bo=/^(?:javascript|jscript|ecmascript|vbscript|mocha|livescript)\s*:/i,bp=/&|\+|\u00AD|\.\.|\/\*|%[^;]|base64|url|expression|data|mailto/i,bq=/^\s*([{:#%.=,>+\[\]@()"';]|\*=?|\$=|\|=|\^=|~=|[a-zA-Z_][a-zA-Z0-9_\-]*|[0-9]+|<\/|\/\*)/,br=/^\s*([@#!"'};:\-%.=,+\[\]()*_]|[a-zA-Z][a-zA-Z0-9._\-]*|\/\*?|\d+(?:\.\d+)?|<\/)/,bs=/[^a-zA-Z0-9+\-_\/ ]/,bt=/[\[\]\/\\"'*<>.&:(){}+=#]/,bu={outer:bi,html:bi,style:bq,styleproperty:br};typeof Array.isArray!=="function"&&(Array.isArray=function(a){return Object.prototype.toString.apply(a)==="[object Array]"}),typeof Object.create!=="function"&&(Object.create=function(a){bv.prototype=a;return new bv}),typeof Object.keys!=="function"&&(Object.keys=function(a){var b=[],c;for(c in a)bw(a,c)&&b.push(c);return b}),typeof String.prototype.entityify!=="function"&&(String.prototype.entityify=function(){return this.replace(/&/g,"&").replace(//g,">")}),typeof String.prototype.isAlpha!=="function"&&(String.prototype.isAlpha=function(){return this>="a"&&this<="z￿"||this>="A"&&this<="Z￿"}),typeof String.prototype.isDigit!=="function"&&(String.prototype.isDigit=function(){return this>="0"&&this<="9"}),typeof String.prototype.supplant!=="function"&&(String.prototype.supplant=function(a){return this.replace(/\{([^{}]*)\}/g,function(b,c){var d=a[c];return typeof d==="string"||typeof d==="number"?d:b})}),typeof String.prototype.name!=="function"&&(String.prototype.name=function(){if(bn.test(this))return this;if(bj.test(this))return'"'+this.replace(bk,function(a){var b=u[a];if(b)return b;return"\\u"+("0000"+a.charCodeAt().toString(16)).slice(-4)})+'"';return'"'+this+'"'});var bE=function bE(){function f(d,e){var f,g;d==="(color)"||d==="(range)"?g={type:d}:d==="(punctuator)"||d==="(identifier)"&&bw(Y,e)?g=Y[e]||Y["(error)"]:g=Y[d],g=Object.create(g),(d==="(string)"||d==="(range)")&&bo.test(e)&&bB("Script URL.",c,b),d==="(identifier)"&&(g.identifier=!0,e==="__iterator__"||e==="__proto__"?bD("Reserved name '{a}'.",c,b,e):N.nomen&&(e.charAt(0)==="_"||e.charAt(e.length-1)==="_")&&bB("Unexpected {a} in '{b}'.",c,b,"dangling '_'",e)),g.value=e,g.line=c,g.character=a,g.from=b,f=g.id,f!=="(endline)"&&(P=f&&("(,=:[!&|?{};".indexOf(f.charAt(f.length-1))>=0||f==="return"));return g}function e(){var b;if(c>=G.length)return!1;a=1,d=G[c],c+=1,b=d.search(/ \t/),b>=0&&bB("Mixed spaces and tabs.",c,b+1),d=d.replace(/\t/g,Z),b=d.search(bg),b>=0&&bB("Unsafe character.",c,b),N.maxlen&&N.maxlen=32&&e<=126&&e!==34&&e!==92&&e!==39&&bB("Unnecessary escapement.",c,a),a+=b,h=String.fromCharCode(e)}var h,i,j="";E&&g!=='"'&&bB("Strings must use doublequote.",c,a);if(be===g||bd==="scriptstring"&&!be)return f("(punctuator)",g);i=0;for(;;){while(i>=d.length)i=0,(bd!=="html"||!e())&&bD("Unclosed string.",c,b);h=d.charAt(i);if(h===g){a+=1,d=d.substr(i+1);return f("(string)",j,g)}if(h<" "){if(h==="\n"||h==="\r")break;bB("Control character in string: {a}.",c,a+i,d.slice(0,i))}else if(h===be)bB("Bad HTML string",c,a+i);else if(h==="<")N.safe&&bd==="html"?bB("ADsafe string violation.",c,a+i):d.charAt(i+1)==="/"&&(bd||N.safe)?bB("Expected '<\\/' and instead saw '0){a+=1,d=d.slice(m);break}if(!e())return f("(end)","")}q=r(bu[bd]||bh);if(!q){q="",h="";while(d&&d<"!")d=d.substr(1);if(d){if(bd==="html")return f("(error)",d.charAt(0));bD("Unexpected '{a}'.",c,a,d.substr(0,1))}}else{if(h.isAlpha()||h==="_"||h==="$")return f("(identifier)",q);if(h.isDigit()){bd!=="style"&&!isFinite(Number(q))&&bB("Bad number '{a}'.",c,a,q),bd!=="style"&&bd!=="styleproperty"&&d.substr(0,1).isAlpha()&&bB("Missing space after '{a}'.",c,a,q),h==="0"&&(j=q.substr(1,1),j.isDigit()?$.id!=="."&&bd!=="styleproperty"&&bB("Don't use extra leading zeros '{a}'.",c,a,q):E&&(j==="x"||j==="X")&&bB("Avoid 0x-. '{a}'.",c,a,q)),q.substr(q.length-1)==="."&&bB("A trailing decimal point can be confused with a dot '{a}'.",c,a,q);return f("(number)",q)}switch(q){case'"':case"'":return s(q);case"//":T||bd&&bd!=="script"?bB("Unexpected comment.",c,a):bd==="script"&&/<\s*\//i.test(d)?bB("Unexpected =0)break;e()?N.safe&&bf.test(d)&&bB("ADsafe comment violation.",c,a):bD("Unclosed comment.",c,a)}a+=m+2,d.substr(m,1)==="/"&&bD("Nested comment.",c,a),d=d.substr(m+2),$.comment=!0;break;case"/*members":case"/*member":case"/*jshint":case"/*global":case"*/":return{value:q,type:"special",line:c,character:a,from:b};case"":break;case"/":$.id==="/="&&bD("A regular expression literal can be confused with '/='.",c,b);if(P){k=0,i=0,n=0;for(;;){g=!0,h=d.charAt(n),n+=1;switch(h){case"":bD("Unclosed regular expression.",c,b);return;case"/":k>0&&bB("Unescaped '{a}'.",c,b+n,"/"),h=d.substr(0,n-1),p={g:!0,i:!0,m:!0};while(p[d.charAt(n)]===!0)p[d.charAt(n)]=!1,n+=1;a+=n,d=d.substr(n),p=d.charAt(0),(p==="/"||p==="*")&&bD("Confusing regular expression.",c,b);return f("(regexp)",h);case"\\":h=d.charAt(n),h<" "?bB("Unexpected control character in regular expression.",c,b+n):h==="<"&&bB("Unexpected escaped character '{a}' in regular expression.",c,b+n,h),n+=1;break;case"(":k+=1,g=!1;if(d.charAt(n)==="?"){n+=1;switch(d.charAt(n)){case":":case"=":case"!":n+=1;break;default:bB("Expected '{a}' and instead saw '{b}'.",c,b+n,":",d.charAt(n))}}else i+=1;break;case"|":g=!1;break;case")":k===0?bB("Unescaped '{a}'.",c,b+n,")"):k-=1;break;case" ":p=1;while(d.charAt(n)===" ")n+=1,p+=1;p>1&&bB("Spaces are hard to count. Use {{a}}.",c,b+n,p);break;case"[":h=d.charAt(n),h==="^"&&(n+=1,N.regexp?bB("Insecure '{a}'.",c,b+n,h):d.charAt(n)==="]"&&bD("Unescaped '{a}'.",c,b+n,"^")),p=!1,h==="]"&&(bB("Empty class.",c,b+n-1),p=!0);klass:do{h=d.charAt(n),n+=1;switch(h){case"[":case"^":bB("Unescaped '{a}'.",c,b+n,h),p=!0;break;case"-":p?p=!1:(bB("Unescaped '{a}'.",c,b+n,"-"),p=!0);break;case"]":p||bB("Unescaped '{a}'.",c,b+n-1,"-");break klass;case"\\":h=d.charAt(n),h<" "?bB("Unexpected control character in regular expression.",c,b+n):h==="<"&&bB("Unexpected escaped character '{a}' in regular expression.",c,b+n,h),n+=1,p=!0;break;case"/":bB("Unescaped '{a}'.",c,b+n-1,"/"),p=!0;break;case"<":bd==="script"&&(h=d.charAt(n),(h==="!"||h==="/")&&bB("HTML confusion in regular expression '<{a}'.",c,b+n,h)),p=!0;break;default:p=!0}}while(h);break;case".":N.regexp&&bB("Insecure '{a}'.",c,b+n,h);break;case"]":case"?":case"{":case"}":case"+":case"*":bB("Unescaped '{a}'.",c,b+n,h);break;case"<":bd==="script"&&(h=d.charAt(n),(h==="!"||h==="/")&&bB("HTML confusion in regular expression '<{a}'.",c,b+n,h))}if(g)switch(d.charAt(n)){case"?":case"+":case"*":n+=1,d.charAt(n)==="?"&&(n+=1);break;case"{":n+=1,h=d.charAt(n),(h<"0"||h>"9")&&bB("Expected a number and instead saw '{a}'.",c,b+n,h),n+=1,o=+h;for(;;){h=d.charAt(n);if(h<"0"||h>"9")break;n+=1,o=+h+o*10}l=o;if(h===","){n+=1,l=Infinity,h=d.charAt(n);if(h>="0"&&h<="9"){n+=1,l=+h;for(;;){h=d.charAt(n);if(h<"0"||h>"9")break;n+=1,l=+h+l*10}}}d.charAt(n)!=="}"?bB("Expected '{a}' and instead saw '{b}'.",c,b+n,"}",h):n+=1,d.charAt(n)==="?"&&(n+=1),o>l&&bB("'{a}' should not be greater than '{b}'.",c,b+n,o,l)}}h=d.substr(0,n-1),a+=n,d=d.substr(n);return f("(regexp)",h)}return f("(punctuator)",q);case".",c,a),a+=3,d=d.slice(m+3);break;case"#":if(bd==="html"||bd==="styleproperty"){for(;;){h=d.charAt(0);if((h<"0"||h>"9")&&(h<"a"||h>"f")&&(h<"A"||h>"F"))break;a+=1,d=d.substr(1),q+=h}q.length!==4&&q.length!==7&&bB("Bad hex color '{a}'.",c,b+n,q);return f("(color)",q)}return f("(punctuator)",q);default:if(bd==="outer"&&h==="&"){a+=1,d=d.substr(1);for(;;){h=d.charAt(0),a+=1,d=d.substr(1);if(h===";")break;h>="0"&&h<="9"||h>="a"&&h<="z"||h==="#"||bD("Bad entity",c,b+n,a)}break}return f("(punctuator)",q)}}}}}}();m=[cB,function(){for(;;)if(K.identifier)switch(K.value.toLowerCase()){case"url":cB();break;case"expression":bA("Unexpected expression '{a}'.",K,K.value),bI();break;default:bI()}else{if(K.id===";"||K.id==="!"||K.id==="(end)"||K.id==="}")return!0;bI()}}],o=["none","dashed","dotted","double","groove","hidden","inset","outset","ridge","solid"],p=["auto","always","avoid","left","right"],r={all:!0,braille:!0,embossed:!0,handheld:!0,print:!0,projection:!0,screen:!0,speech:!0,tty:!0,tv:!0},s=["auto","hidden","scroll","visible"],l={background:[!0,"background-attachment","background-color","background-image","background-position","background-repeat"],"background-attachment":["scroll","fixed"],"background-color":["transparent",cs],"background-image":["none",cB],"background-position":[2,[ct,"top","bottom","left","right","center"]],"background-repeat":["repeat","repeat-x","repeat-y","no-repeat"],border:[!0,"border-color","border-style","border-width"],"border-bottom":[!0,"border-bottom-color","border-bottom-style","border-bottom-width"],"border-bottom-color":cs,"border-bottom-style":o,"border-bottom-width":cv,"border-collapse":["collapse","separate"],"border-color":["transparent",4,cs],"border-left":[!0,"border-left-color","border-left-style","border-left-width"],"border-left-color":cs,"border-left-style":o,"border-left-width":cv,"border-right":[!0,"border-right-color","border-right-style","border-right-width"],"border-right-color":cs,"border-right-style":o,"border-right-width":cv,"border-spacing":[2,ct],"border-style":[4,o],"border-top":[!0,"border-top-color","border-top-style","border-top-width"],"border-top-color":cs,"border-top-style":o,"border-top-width":cv,"border-width":[4,cv],bottom:[ct,"auto"],"caption-side":["bottom","left","right","top"],clear:["both","left","none","right"],clip:[cA,"auto"],color:cs,content:["open-quote","close-quote","no-open-quote","no-close-quote",cr,cB,cz,cx],"counter-increment":[cp,"none"],"counter-reset":[cp,"none"],cursor:[cB,"auto","crosshair","default","e-resize","help","move","n-resize","ne-resize","nw-resize","pointer","s-resize","se-resize","sw-resize","w-resize","text","wait"],direction:["ltr","rtl"],display:["block","compact","inline","inline-block","inline-table","list-item","marker","none","run-in","table","table-caption","table-cell","table-column","table-column-group","table-footer-group","table-header-group","table-row","table-row-group"],"empty-cells":["show","hide"],"float":["left","none","right"],font:["caption","icon","menu","message-box","small-caption","status-bar",!0,"font-size","font-style","font-weight","font-family"],"font-family":cy,"font-size":["xx-small","x-small","small","medium","large","x-large","xx-large","larger","smaller",ct],"font-size-adjust":["none",cq],"font-stretch":["normal","wider","narrower","ultra-condensed","extra-condensed","condensed","semi-condensed","semi-expanded","expanded","extra-expanded"],"font-style":["normal","italic","oblique"],"font-variant":["normal","small-caps"],"font-weight":["normal","bold","bolder","lighter",cq],height:[ct,"auto"],left:[ct,"auto"],"letter-spacing":["normal",ct],"line-height":["normal",cu],"list-style":[!0,"list-style-image","list-style-position","list-style-type"],"list-style-image":["none",cB],"list-style-position":["inside","outside"],"list-style-type":["circle","disc","square","decimal","decimal-leading-zero","lower-roman","upper-roman","lower-greek","lower-alpha","lower-latin","upper-alpha","upper-latin","hebrew","katakana","hiragana-iroha","katakana-oroha","none"],margin:[4,cw],"margin-bottom":cw,"margin-left":cw,"margin-right":cw,"margin-top":cw,"marker-offset":[ct,"auto"],"max-height":[ct,"none"],"max-width":[ct,"none"],"min-height":ct,"min-width":ct,opacity:cq,outline:[!0,"outline-color","outline-style","outline-width"],"outline-color":["invert",cs],"outline-style":["dashed","dotted","double","groove","inset","none","outset","ridge","solid"],"outline-width":cv,overflow:s,"overflow-x":s,"overflow-y":s,padding:[4,ct],"padding-bottom":ct,"padding-left":ct,"padding-right":ct,"padding-top":ct,"page-break-after":p,"page-break-before":p,position:["absolute","fixed","relative","static"],quotes:[8,cr],right:[ct,"auto"],"table-layout":["auto","fixed"],"text-align":["center","justify","left","right"],"text-decoration":["none","underline","overline","line-through","blink"],"text-indent":ct,"text-shadow":["none",4,[cs,ct]],"text-transform":["capitalize","uppercase","lowercase","none"],top:[ct,"auto"],"unicode-bidi":["normal","embed","bidi-override"],"vertical-align":["baseline","bottom","sub","super","top","text-top","middle","text-bottom",ct],visibility:["visible","hidden","collapse"],"white-space":["normal","nowrap","pre","pre-line","pre-wrap","inherit"],width:[ct,"auto"],"word-spacing":["normal",ct],"word-wrap":["break-word","normal"],"z-index":["auto",cq]},bY("(number)",function(){return this}),bY("(string)",function(){return this}),Y["(identifier)"]={type:"(identifier)",lbp:0,identifier:!0,nud:function(){var a=this.value,b=S[a],c;typeof b==="function"?b=undefined:typeof b==="boolean"&&(c=v,v=x[0],bF(a,"var"),b=v,v=c);if(v===b)switch(v[a]){case"unused":v[a]="var";break;case"unction":v[a]="function",this["function"]=!0;break;case"function":this["function"]=!0;break;case"label":bA("'{a}' is a statement label.",$,a)}else if(v["(global)"])e!="typeof"&&e!="delete"&&N.undef&&typeof O[a]!=="boolean"&&bA("'{a}' is not defined.",$,a),co($);else switch(v[a]){case"closure":case"function":case"var":case"unused":bA("'{a}' used out of scope.",$,a);break;case"label":bA("'{a}' is a statement label.",$,a);break;case"outer":case"global":break;default:if(b===!0)v[a]=!0;else if(b===null)bA("'{a}' is not allowed.",$,a),co($);else if(typeof b!=="object")e!="typeof"&&e!="delete"&&N.undef?bA("'{a}' is not defined.",$,a):v[a]=!0,co($);else switch(b[a]){case"function":case"unction":this["function"]=!0,b[a]="closure",v[a]=b["(global)"]?"global":"outer";break;case"var":case"unused":b[a]="closure",v[a]=b["(global)"]?"global":"outer";break;case"closure":case"parameter":v[a]=b["(global)"]?"global":"outer";break;case"label":bA("'{a}' is a statement label.",$,a)}}return this},led:function(){bC("Expected an operator and instead saw '{a}'.",K,K.value)}},bY("(regexp)",function(){return this}),bT("(endline)"),bT("(begin)"),bT("(end)").reach=!0,bT(""),bT("(error)").reach=!0,bT("}").reach=!0,bT(")"),bT("]"),bT('"').reach=!0,bT("'").reach=!0,bT(";"),bT(":").reach=!0,bT(","),bT("#"),bT("@"),bZ("else"),bZ("case").reach=!0,bZ("catch"),bZ("default").reach=!0,bZ("finally"),b$("arguments",function(a){X&&v["(global)"]?bA("Strict violation.",a):N.safe&&bA("ADsafe violation.",a)}),b$("eval",function(a){N.safe&&bA("ADsafe violation.",a)}),b$("false"),b$("Infinity"),b$("NaN"),b$("null"),b$("this",function(a){X&&(v["(statement)"]&&v["(name)"].charAt(0)>"Z"||v["(global)"])?bA("Strict violation.",a):N.safe&&bA("ADsafe violation.",a)}),b$("true"),b$("undefined"),cc("=","assign",20),cc("+=","assignadd",20),cc("-=","assignsub",20),cc("*=","assignmult",20),cc("/=","assigndiv",20).nud=function(){bC("A regular expression literal can be confused with '/='.")},cc("%=","assignmod",20),ce("&=","assignbitand",20),ce("|=","assignbitor",20),ce("^=","assignbitxor",20),ce("<<=","assignshiftleft",20),ce(">>=","assignshiftright",20),ce(">>>=","assignshiftrightunsigned",20),b_("?",function(a,b){b.left=a,b.right=bJ(10),bI(":"),b["else"]=bJ(10);return b},30),b_("||","or",40),b_("&&","and",50),cd("|","bitor",70),cd("^","bitxor",80),cd("&","bitand",90),ca("==",function(a,b){N.eqeqeq?bA("Expected '{a}' and instead saw '{b}'.",this,"===","=="):cb(a)?bA("Use '{a}' to compare with '{b}'.",this,"===",a.value):cb(b)&&bA("Use '{a}' to compare with '{b}'.",this,"===",b.value);return this}),ca("==="),ca("!=",function(a,b){N.eqeqeq?bA("Expected '{a}' and instead saw '{b}'.",this,"!==","!="):cb(a)?bA("Use '{a}' to compare with '{b}'.",this,"!==",a.value):cb(b)&&bA("Use '{a}' to compare with '{b}'.",this,"!==",b.value);return this}),ca("!=="),ca("<"),ca(">"),ca("<="),ca(">="),cd("<<","shiftleft",120),cd(">>","shiftright",120),cd(">>>","shiftrightunsigned",120),b_("in","in",120),b_("instanceof","instanceof",120),b_("+",function(a,b){var c=bJ(130);if(a&&c&&a.id==="(string)"&&c.id==="(string)"){a.value+=c.value,a.character=c.character,bo.test(a.value)&&bA("JavaScript URL.",a);return a}b.left=a,b.right=c;return b},130),bX("+","num"),bX("+++",function(){bA("Confusing pluses."),this.right=bJ(150),this.arity="unary";return this}),b_("+++",function(a){bA("Confusing pluses."),this.left=a,this.right=bJ(130);return this},130),b_("-","sub",130),bX("-","neg"),bX("---",function(){bA("Confusing minuses."),this.right=bJ(150),this.arity="unary";return this}),b_("---",function(a){bA("Confusing minuses."),this.left=a,this.right=bJ(130);return this},130),b_("*","mult",140),b_("/","div",140),b_("%","mod",140),cf("++","postinc"),bX("++","preinc"),Y["++"].exps=!0,cf("--","postdec"),bX("--","predec"),Y["--"].exps=!0,bX("delete",function(){var a=bJ(0);(!a||a.id!=="."&&a.id!=="[")&&bA("Variables should not be deleted."),this.first=a;return this}).exps=!0,bX("~",function(){N.bitwise&&bA("Unexpected '{a}'.",this,"~"),bJ(150);return this}),bX("!",function(){this.right=bJ(150),this.arity="unary",g[this.right.id]===!0&&bA("Confusing use of '{a}'.",this,"!");return this}),bX("typeof","typeof"),bX("new",function(){var a=bJ(155),b;if(a&&a.id!=="function")if(a.identifier){a["new"]=!0;switch(a.value){case"Object":bA("Use the object literal notation {}.",$);break;case"Array":K.id!=="("?bA("Use the array literal notation [].",$):(bI("("),K.id===")"&&bA("Use the array literal notation [].",$),bI(")")),this.first=a;return this;case"Number":case"String":case"Boolean":case"Math":case"JSON":bA("Do not use {a} as a constructor.",$,a.value);break;case"Function":N.evil||bA("The Function constructor is eval.");break;case"Date":case"RegExp":break;default:a.id!=="function"&&(b=a.value.substr(0,1),N.newcap&&(b<"A"||b>"Z")&&bA("A constructor name should start with an uppercase letter.",$))}}else a.id!=="."&&a.id!=="["&&a.id!=="("&&bA("Bad constructor.",$);else bA("Weird construction. Delete 'new'.",this);bK($,K),K.id!=="("&&bA("Missing '()' invoking a constructor."),this.first=a;return this}),Y["new"].exps=!0,b_(".",function(d,e){bK(Q,$),bL();var f=ch();typeof f==="string"&&cn(f),e.left=d,e.right=f,N.noarg&&d&&d.value==="arguments"&&(f==="callee"||f==="caller")?bA("Avoid arguments.{a}.",d,f):!N.evil&&d&&d.value==="document"&&(f==="write"||f==="writeln")?bA("document.write can be a form of eval.",d):N.adsafe&&d&&d.value==="ADSAFE"&&(f==="id"||f==="lib"?bA("ADsafe violation.",e):f==="go"&&(bd!=="script"?bA("ADsafe violation.",e):(c||K.id!=="("||bH(0).id!=="(string)"||bH(0).value!==a||bH(1).id!==",")&&bC("ADsafe violation: go.",e),c=!0,b=!1));if(!!N.evil||f!=="eval"&&f!=="execScript"){if(N.safe)for(;;){h[f]===!0&&bA("ADsafe restricted word '{a}'.",$,f);if(typeof O[d.value]!=="boolean"||K.id==="(")break;if(W[f]===!0){K.id==="."&&bA("ADsafe violation.",e);break}if(K.id!=="."){bA("ADsafe violation.",e);break}bI("."),$.left=e,$.right=f,e=$,f=ch(),typeof f==="string"&&cn(f)}}else bA("eval is evil.");return e},160,!0),b_("(",function(a,b){Q.id!=="}"&&Q.id!==")"&&bL(Q,$),bM(),N.immed&&!a.immed&&a.id==="function"&&bA("Wrap an immediate function invocation in parentheses to assist the reader in understanding that the expression is the result of a function, and not the function itself.");var c=0,d=[];a&&(a.type==="(identifier)"?a.value.match(/^[A-Z]([A-Z0-9_$]*[a-z][A-Za-z0-9_$]*)?$/)&&a.value!=="Number"&&a.value!=="String"&&a.value!=="Boolean"&&a.value!=="Date"&&(a.value==="Math"?bA("Math is not a function.",a):N.newcap&&bA("Missing 'new' prefix when invoking a constructor.",a)):a.id==="."&&N.safe&&a.left.value==="Math"&&a.right==="random"&&bA("ADsafe violation.",a));if(K.id!==")")for(;;){d[d.length]=bJ(10),c+=1;if(K.id!==",")break;bR()}bI(")"),bM(Q,$),typeof a==="object"&&(a.value==="parseInt"&&c===1&&bA("Missing radix parameter.",a),N.evil||(a.value==="eval"||a.value==="Function"||a.value==="execScript"?bA("eval is evil.",a):d[0]&&d[0].id==="(string)"&&(a.value==="setTimeout"||a.value==="setInterval")&&bA("Implied eval is evil. Pass a function instead of a string.",a)),!a.identifier&&a.id!=="."&&a.id!=="["&&a.id!=="("&&a.id!=="&&"&&a.id!=="||"&&a.id!=="?"&&bA("Bad invocation.",a)),b.left=a;return b},155,!0).exps=!0,bX("(",function(){bM(),K.id==="function"&&(K.immed=!0);var a=bJ(0);bI(")",this),bM(Q,$),N.immed&&a.id==="function"&&(K.id==="("?bA("Move the invocation into the parens that contain the function.",K):bA("Do not wrap function literals in parens unless they are to be immediately invoked.",this));return a}),b_("[",function(a,b){bL(Q,$),bM();var c=bJ(0),d;c&&c.type==="(string)"?(N.safe&&h[c.value]===!0?bA("ADsafe restricted word '{a}'.",b,c.value):!!N.evil||c.value!=="eval"&&c.value!=="execScript"?N.safe&&(c.value.charAt(0)==="_"||c.value.charAt(0)==="-")&&bA("ADsafe restricted subscript '{a}'.",b,c.value):bA("eval is evil.",b),cn(c.value),!N.sub&&bn.test(c.value)&&(d=Y[c.value],(!d||!d.reserved)&&bA("['{a}'] is better written in dot notation.",c,c.value))):(!c||c.type!=="(number)"||c.value<0)&&N.safe&&bA("ADsafe subscripting."),bI("]",b),bM(Q,$),b.left=a,b.right=c;return b},160,!0),bX("[",function(){var a=$.line!==K.line;this.first=[],a&&(D+=N.indent,K.from===D+N.indent&&(D+=N.indent));while(K.id!=="(end)"){while(K.id===",")bA("Extra comma."),bI(",");if(K.id==="]")break;a&&$.line!==K.line&&bP(),this.first.push(bJ(10));if(K.id!==",")break;bR();if(K.id==="]"&&!N.es5){bA("Extra comma.",$);break}}a&&(D-=N.indent,bP()),bI("]",this);return this},160),function(a){a.nud=function(){var a,b,c,d,e,f={},g;a=$.line!==K.line,a&&(D+=N.indent,K.from===D+N.indent&&(D+=N.indent));for(;;){if(K.id==="}")break;a&&bP();if(K.value==="get"&&bH().id!==":")bI("get"),N.es5||bC("get/set are ES5 features."),c=cP(),c||bC("Missing property name."),g=K,bK($,K),b=cR(c),v["(loopage)"]&&bA("Don't make functions within a loop.",g),e=b["(params)"],e&&bA("Unexpected parameter '{a}' in get {b} function.",g,e[0],c),bK($,K),bI(","),bP(),bI("set"),d=cP(),c!==d&&bC("Expected {a} and instead saw {b}.",$,c,d),g=K,bK($,K),b=cR(c),e=b["(params)"],(!e||e.length!==1||e[0]!=="value")&&bA("Expected (value) in set {a} function.",g,c);else{c=cP();if(typeof c!=="string")break;bI(":"),bN($,K),bJ(10)}f[c]===!0&&bA("Duplicate member '{a}'.",K,c),f[c]=!0,cn(c);if(K.id===",")bR(),K.id===","?bA("Extra comma.",$):K.id==="}"&&!N.es5&&bA("Extra comma.",$);else break}a&&(D-=N.indent,bP()),bI("}",this);return this},a.fud=function(){bC("Expected to see a statement and instead saw a block.",$)}}(bT("{"));var cS=function cS(a){var b,c,d;v["(onevar)"]&&N.onevar?bA("Too many var statements."):v["(global)"]||(v["(onevar)"]=!0),this.first=[];for(;;){bN($,K),b=ch(),v["(global)"]&&O[b]===!1&&bA("Redefinition of '{a}'.",$,b),bF(b,"unused");if(a)break;c=$,this.first.push($),K.id==="="&&(bN($,K),bI("="),bN($,K),K.id==="undefined"&&bA("It is not necessary to initialize '{a}' to 'undefined'.",$,b),bH(0).id==="="&&K.identifier&&bC("Variable {a} was not declared correctly.",K,K.value),d=bJ(0),c.first=d);if(K.id!==",")break;bR()}return this};bU("var",cS).exps=!0,bV("function",function(){C&&bA("Function statements should not be placed in blocks. Use a function expression or move the statement to the top of the outer function.",$);var a=ch();bK($,K),bF(a,"unction"),cR(a,!0),K.id==="("&&K.line===$.line&&bC("Function statements are not invocable. Wrap the whole function invocation in parens.");return this}),bX("function",function(){var a=cg();a?bK($,K):bN($,K),cR(a),v["(loopage)"]&&bA("Don't make functions within a loop.");return this}),bV("if",function(){var a=K;bI("("),bN(this,a),bM(),bJ(20),K.id==="="&&(N.boss||bA("Expected a conditional expression and instead saw an assignment."),bI("="),bJ(20)),bI(")",a),bM(Q,$),cm(!0,!0),K.id==="else"&&(bN($,K),bI("else"),K.id==="if"||K.id==="switch"?cj(!0):cm(!0,!0));return this}),bV("try",function(){var a,b,c;N.adsafe&&bA("ADsafe try violation.",this),cm(!1),K.id==="catch"&&(bI("catch"),bN($,K),bI("("),c=S,S=Object.create(c),b=K.value,K.type!=="(identifier)"?bA("Expected an identifier and instead saw '{a}'.",K,b):bF(b,"exception"),bI(),bI(")"),cm(!1),a=!0,S=c);if(K.id==="finally")bI("finally"),cm(!1);else{a||bC("Expected '{a}' and instead saw '{b}'.",K,"catch",K.value);return this}}),bV("while",function(){var a=K;v["(breakage)"]+=1,v["(loopage)"]+=1,bI("("),bN(this,a),bM(),bJ(20),K.id==="="&&(N.boss||bA("Expected a conditional expression and instead saw an assignment."),bI("="),bJ(20)),bI(")",a),bM(Q,$),cm(!0,!0),v["(breakage)"]-=1,v["(loopage)"]-=1;return this}).labelled=!0,bZ("with"),bV("switch",function(){var a=K,b=!1;v["(breakage)"]+=1,bI("("),bN(this,a),bM(),this.condition=bJ(20),bI(")",a),bM(Q,$),bN($,K),a=K,bI("{"),bN($,K),D+=N.indent,this.cases=[];for(;;)switch(K.id){case"case":switch(v["(verb)"]){case"break":case"case":case"continue":case"return":case"switch":case"throw":break;default:bA("Expected a 'break' statement before 'case'.",$)}bP(-N.indent),bI("case"),this.cases.push(bJ(20)),b=!0,bI(":"),v["(verb)"]="case";break;case"default":switch(v["(verb)"]){case"break":case"continue":case"return":case"throw":break;default:bA("Expected a 'break' statement before 'default'.",$)}bP(-N.indent),bI("default"),b=!0,bI(":");break;case"}":D-=N.indent,bP(),bI("}",a),(this.cases.length===1||this.condition.id==="true"||this.condition.id==="false")&&bA("This 'switch' should be an 'if'.",this),v["(breakage)"]-=1,v["(verb)"]=undefined;return;case"(end)":bC("Missing '{a}'.",K,"}");return;default:if(b)switch($.id){case",":bC("Each value should have its own case label.");return;case":":cl();break;default:bC("Missing ':' on a case clause.",$)}else bC("Expected '{a}' and instead saw '{b}'.",K,"case",K.value)}}).labelled=!0,bU("debugger",function(){N.debug||bA("All 'debugger' statements should be removed.");return this}).exps=!0,function(){var a=bU("do",function(){v["(breakage)"]+=1,v["(loopage)"]+=1,this.first=cm(!0),bI("while");var a=K;bN($,a),bI("("),bM(),bJ(20),K.id==="="&&(N.boss||bA("Expected a conditional expression and instead saw an assignment."),bI("="),bJ(20)),bI(")",a),bM(Q,$),v["(breakage)"]-=1,v["(loopage)"]-=1;return this});a.labelled=!0,a.exps=!0}(),bV("for",function(){var a=N.forin,b,c=K;v["(breakage)"]+=1,v["(loopage)"]+=1,bI("("),bN(this,c),bM();if(bH(K.id==="var"?1:0).id==="in"){if(K.id==="var")bI("var"),cS(!0);else{switch(v[K.value]){case"unused":v[K.value]="var";break;case"var":break;default:bA("Bad for in variable '{a}'.",K,K.value)}bI()}bI("in"),bJ(20),bI(")",c),b=cm(!0,!0),!a&&(b.length>1||typeof b[0]!=="object"||b[0].value!=="if")&&bA("The body of a for in should be wrapped in an if statement to filter unwanted properties from the prototype.",this),v["(breakage)"]-=1,v["(loopage)"]-=1;return this}if(K.id!==";")if(K.id==="var")bI("var"),cS();else for(;;){bJ(0,"for");if(K.id!==",")break;bR()}bQ($),bI(";"),K.id!==";"&&(bJ(20),K.id==="="&&(N.boss||bA("Expected a conditional expression and instead saw an assignment."),bI("="),bJ(20))),bQ($),bI(";"),K.id===";"&&bC("Expected '{a}' and instead saw '{b}'.",K,")",";");if(K.id!==")")for(;;){bJ(0,"for");if(K.id!==",")break;bR()}bI(")",c),bM(Q,$),cm(!0,!0),v["(breakage)"]-=1,v["(loopage)"]-=1;return this}).labelled=!0,bU("break",function(){var a=K.value;v["(breakage)"]===0&&bA("Unexpected '{a}'.",K,this.value),bQ(this),K.id!==";"&&$.line===K.line&&(v[a]!=="label"?bA("'{a}' is not a statement label.",K,a):S[a]!==v&&bA("'{a}' is out of scope.",K,a),this.first=K,bI()),ci("break");return this}).exps=!0,bU("continue",function(){var a=K.value;v["(breakage)"]===0&&bA("Unexpected '{a}'.",K,this.value),bQ(this),K.id!==";"?$.line===K.line&&(v[a]!=="label"?bA("'{a}' is not a statement label.",K,a):S[a]!==v&&bA("'{a}' is out of scope.",K,a),this.first=K,bI()):v["(loopage)"]||bA("Unexpected '{a}'.",K,this.value),ci("continue");return this}).exps=!0,bU("return",function(){bQ(this),K.id==="(regexp)"&&bA("Wrap the /regexp/ literal in parens to disambiguate the slash operator."),K.id!==";"&&!K.reach&&(bN($,K),this.first=bJ(20)),ci("return");return this}).exps=!0,bU("throw",function(){bQ(this),bN($,K),this.first=bJ(20),ci("throw");return this}).exps=!0,bZ("void"),bZ("class"),bZ("const"),bZ("enum"),bZ("export"),bZ("extends"),bZ("import"),bZ("super"),bZ("let"),bZ("yield"),bZ("implements"),bZ("interface"),bZ("package"),bZ("private"),bZ("protected"),bZ("public"),bZ("static");var cU=function(e,g){var h,i,j;d.errors=[],O=Object.create(V);if(g){h=g.predef;if(h)if(Array.isArray(h))for(i=0;i",K.value),K.value==="use strict"&&(bA('Use the function form of "use strict".'),ck()),cl("lib")}bI("(end)")}catch(k){k&&d.errors.push({reason:k.message,line:k.line||K.line,character:k.character||K.from},null)}return d.errors.length===0};cU.data=function(){var a={functions:[]},b,c,d=[],e,f,g,h=[],i,j=[],k;cU.errors.length&&(a.errors=cU.errors),E&&(a.json=!0);for(i in B)bw(B,i)&&d.push({name:i,line:B[i]});d.length>0&&(a.implieds=d),_.length>0&&(a.urls=_),c=Object.keys(S),c.length>0&&(a.globals=c);for(f=1;f0&&(a.unused=j),h=[];for(i in I)if(typeof I[i]==="number"){a.member=I;break}return a},cU.report=function(a){function o(a,b){var c,d,e;if(b){m.push("
    "+a+" "),b=b.sort();for(d=0;d")}}var b=cU.data(),c=[],d,e,f,g,h,i,j,k="",l,m=[],n;if(b.errors||b.implieds||b.unused){f=!0,m.push("
    Error:");if(b.errors)for(h=0;hProblem"+(isFinite(d.line)?" at line "+d.line+" character "+d.character:"")+": "+d.reason.entityify()+"

    "+(e&&(e.length>80?e.slice(0,77)+"...":e).entityify())+"

    "));if(b.implieds){n=[];for(h=0;h"+b.implieds[h].name+" "+b.implieds[h].line+"";m.push("

    Implied global: "+n.join(", ")+"

    ")}if(b.unused){n=[];for(h=0;h"+b.unused[h].name+" "+b.unused[h].line+" "+b.unused[h]["function"]+"";m.push("

    Unused variable: "+n.join(", ")+"

    ")}b.json&&m.push("

    JSON: bad.

    "),m.push("
    ")}if(!a){m.push("
    "),b.urls&&o("URLs
    ",b.urls,"
    "),bd==="style"?m.push("

    CSS.

    "):b.json&&!f?m.push("

    JSON: good.

    "):b.globals?m.push("
    Global "+b.globals.sort().join(", ")+"
    "):m.push("
    No new global variables introduced.
    ");for(h=0;h
    "+g.line+"-"+g.last+" "+(g.name||"")+"("+(g.param?g.param.join(", "):"")+")
    "),o("Unused",g.unused),o("Closure",g.closure),o("Variable",g["var"]),o("Exception",g.exception),o("Outer",g.outer),o("Global",g.global),o("Label",g.label);if(b.member){c=Object.keys(b.member);if(c.length){c=c.sort(),k="
    /*members ",j=10;for(h=0;h72&&(m.push(k+"
    "),k=" ",j=1),j+=l.length+2,b.member[i]===1&&(l=""+l+""),h*/
    ")}m.push("
    ")}}return m.join("")},cU.jshint=cU,cU.edition="2011-02-19";return cU}();typeof b=="object"&&b&&(b.JSHINT=d)}),define("ace/narcissus/jsparse",function(require,exports,module){function parseStdin(a,b){for(;;)try{var c=new lexer.Tokenizer(a,"stdin",b.value),d=Script(c,!1);b.value=c.lineno;return d}catch(e){if(!c.unexpectedEOF)throw e;var f=readline();if(!f)throw e;a+="\n"+f}}function parse(a,b,c){var d=new lexer.Tokenizer(a,b,c),e=Script(d,!1);if(!d.done)throw d.newSyntaxError("Syntax error");return e}function PrimaryExpression(a,b){var c,d,e=a.get(!0);switch(e){case FUNCTION:c=FunctionDefinition(a,b,!1,EXPRESSED_FORM);break;case LEFT_BRACKET:c=new Node(a,{type:ARRAY_INIT});while((e=a.peek(!0))!==RIGHT_BRACKET){if(e===COMMA){a.get(),c.push(null);continue}c.push(AssignExpression(a,b));if(e!==COMMA&&!a.match(COMMA))break}c.children.length===1&&a.match(FOR)&&(d=new Node(a,{type:ARRAY_COMP,expression:c.children[0],tail:ComprehensionTail(a,b)}),c=d),a.mustMatch(RIGHT_BRACKET);break;case LEFT_CURLY:var f,g;c=new Node(a,{type:OBJECT_INIT});object_init:if(!a.match(RIGHT_CURLY)){do{e=a.get();if(a.token.value!=="get"&&a.token.value!=="set"||a.peek()!==IDENTIFIER){switch(e){case IDENTIFIER:case NUMBER:case STRING:f=new Node(a,{type:IDENTIFIER});break;case RIGHT_CURLY:if(b.ecma3OnlyMode)throw a.newSyntaxError("Illegal trailing ,");break object_init;default:if(a.token.value in definitions.keywords){f=new Node(a,{type:IDENTIFIER});break}throw a.newSyntaxError("Invalid property name")}if(a.match(COLON))d=new Node(a,{type:PROPERTY_INIT}),d.push(f),d.push(AssignExpression(a,b)),c.push(d);else{if(a.peek()!==COMMA&&a.peek()!==RIGHT_CURLY)throw a.newSyntaxError("missing : after property");c.push(f)}}else{if(b.ecma3OnlyMode)throw a.newSyntaxError("Illegal property accessor");c.push(FunctionDefinition(a,b,!0,EXPRESSED_FORM))}}while(a.match(COMMA));a.mustMatch(RIGHT_CURLY)}break;case LEFT_PAREN:c=ParenExpression(a,b),a.mustMatch(RIGHT_PAREN),c.parenthesized=!0;break;case LET:c=LetBlock(a,b,!1);break;case NULL:case THIS:case TRUE:case FALSE:case IDENTIFIER:case NUMBER:case STRING:case REGEXP:c=new Node(a);break;default:throw a.newSyntaxError("missing operand")}return c}function ArgumentList(a,b){var c,d;c=new Node(a,{type:LIST});if(a.match(RIGHT_PAREN,!0))return c;do{d=AssignExpression(a,b);if(d.type===YIELD&&!d.parenthesized&&a.peek()===COMMA)throw a.newSyntaxError("Yield expression must be parenthesized");if(a.match(FOR)){d=GeneratorExpression(a,b,d);if(c.children.length>1||a.peek(!0)===COMMA)throw a.newSyntaxError("Generator expression must be parenthesized")}c.push(d)}while(a.match(COMMA));a.mustMatch(RIGHT_PAREN);return c}function MemberExpression(a,b,c){var d,e,f,g;a.match(NEW)?(d=new Node(a),d.push(MemberExpression(a,b,!1)),a.match(LEFT_PAREN)&&(d.type=NEW_WITH_ARGS,d.push(ArgumentList(a,b)))):d=PrimaryExpression(a,b);while((g=a.get())!==END){switch(g){case DOT:e=new Node(a),e.push(d),a.mustMatch(IDENTIFIER),e.push(new Node(a));break;case LEFT_BRACKET:e=new Node(a,{type:INDEX}),e.push(d),e.push(Expression(a,b)),a.mustMatch(RIGHT_BRACKET);break;case LEFT_PAREN:if(c){e=new Node(a,{type:CALL}),e.push(d),e.push(ArgumentList(a,b));break};default:a.unget();return d}d=e}return d}function UnaryExpression(a,b){var c,d,e;switch(e=a.get(!0)){case DELETE:case VOID:case TYPEOF:case NOT:case BITWISE_NOT:case PLUS:case MINUS:e===PLUS?c=new Node(a,{type:UNARY_PLUS}):e===MINUS?c=new Node(a,{type:UNARY_MINUS}):c=new Node(a),c.push(UnaryExpression(a,b));break;case INCREMENT:case DECREMENT:c=new Node(a),c.push(MemberExpression(a,b,!0));break;default:a.unget(),c=MemberExpression(a,b,!0),a.tokens[a.tokenIndex+a.lookahead-1&3].lineno===a.lineno&&(a.match(INCREMENT)||a.match(DECREMENT))&&(d=new Node(a,{postfix:!0}),d.push(c),c=d)}return c}function MultiplyExpression(a,b){var c,d;c=UnaryExpression(a,b);while(a.match(MUL)||a.match(DIV)||a.match(MOD))d=new Node(a),d.push(c),d.push(UnaryExpression(a,b)),c=d;return c}function AddExpression(a,b){var c,d;c=MultiplyExpression(a,b);while(a.match(PLUS)||a.match(MINUS))d=new Node(a),d.push(c),d.push(MultiplyExpression(a,b)),c=d;return c}function ShiftExpression(a,b){var c,d;c=AddExpression(a,b);while(a.match(LSH)||a.match(RSH)||a.match(URSH))d=new Node(a),d.push(c),d.push(AddExpression(a,b)),c=d;return c}function RelationalExpression(a,b){var c,d,e=b.update({inForLoopInit:!1});c=ShiftExpression(a,e);while(a.match(LT)||a.match(LE)||a.match(GE)||a.match(GT)||!b.inForLoopInit&&a.match(IN)||a.match(INSTANCEOF))d=new Node(a),d.push(c),d.push(ShiftExpression(a,e)),c=d;return c}function EqualityExpression(a,b){var c,d;c=RelationalExpression(a,b);while(a.match(EQ)||a.match(NE)||a.match(STRICT_EQ)||a.match(STRICT_NE))d=new Node(a),d.push(c),d.push(RelationalExpression(a,b)),c=d;return c}function BitwiseAndExpression(a,b){var c,d;c=EqualityExpression(a,b);while(a.match(BITWISE_AND))d=new Node(a),d.push(c),d.push(EqualityExpression(a,b)),c=d;return c}function BitwiseXorExpression(a,b){var c,d;c=BitwiseAndExpression(a,b);while(a.match(BITWISE_XOR))d=new Node(a),d.push(c),d.push(BitwiseAndExpression(a,b)),c=d;return c}function BitwiseOrExpression(a,b){var c,d;c=BitwiseXorExpression(a,b);while(a.match(BITWISE_OR))d=new Node(a),d.push(c),d.push(BitwiseXorExpression(a,b)),c=d;return c}function AndExpression(a,b){var c,d;c=BitwiseOrExpression(a,b);while(a.match(AND))d=new Node(a),d.push(c),d.push(BitwiseOrExpression(a,b)),c=d;return c}function OrExpression(a,b){var c,d;c=AndExpression(a,b);while(a.match(OR))d=new Node(a),d.push(c),d.push(AndExpression(a,b)),c=d;return c}function ConditionalExpression(a,b){var c,d;c=OrExpression(a,b);if(a.match(HOOK)){d=c,c=new Node(a,{type:HOOK}),c.push(d),c.push(AssignExpression(a,b.update({inForLoopInit:!1})));if(!a.match(COLON))throw a.newSyntaxError("missing : after ?");c.push(AssignExpression(a,b))}return c}function AssignExpression(a,b){var c,d;if(a.match(YIELD,!0))return ReturnOrYield(a,b);c=new Node(a,{type:ASSIGN}),d=ConditionalExpression(a,b);if(!a.match(ASSIGN))return d;switch(d.type){case OBJECT_INIT:case ARRAY_INIT:d.destructuredNames=checkDestructuring(a,b,d);case IDENTIFIER:case DOT:case INDEX:case CALL:break;default:throw a.newSyntaxError("Bad left-hand side of assignment")}c.assignOp=a.token.assignOp,c.push(d),c.push(AssignExpression(a,b));return c}function Expression(a,b){var c,d;c=AssignExpression(a,b);if(a.match(COMMA)){d=new Node(a,{type:COMMA}),d.push(c),c=d;do{d=c.children[c.children.length-1];if(d.type===YIELD&&!d.parenthesized)throw a.newSyntaxError("Yield expression must be parenthesized");c.push(AssignExpression(a,b))}while(a.match(COMMA))}return c}function ParenExpression(a,b){var c=Expression(a,b.update({inForLoopInit:b.inForLoopInit&&a.token.type===LEFT_PAREN}));if(a.match(FOR)){if(c.type===YIELD&&!c.parenthesized)throw a.newSyntaxError("Yield expression must be parenthesized");if(c.type===COMMA&&!c.parenthesized)throw a.newSyntaxError("Generator expression must be parenthesized");c=GeneratorExpression(a,b,c)}return c}function HeadExpression(a,b){var c=MaybeLeftParen(a,b),d=ParenExpression(a,b);MaybeRightParen(a,c);if(c===END&&!d.parenthesized){var e=a.peek();if(e!==LEFT_CURLY&&!definitions.isStatementStartCode[e])throw a.newSyntaxError("Unparenthesized head followed by unbraced body")}return d}function ComprehensionTail(a,b){var c,d,e,f,g;c=new Node(a,{type:COMP_TAIL});do{d=new Node(a,{type:FOR_IN,isLoop:!0}),a.match(IDENTIFIER)&&(a.token.value==="each"?d.isEach=!0:a.unget()),g=MaybeLeftParen(a,b);switch(a.get()){case LEFT_BRACKET:case LEFT_CURLY:a.unget(),d.iterator=DestructuringExpression(a,b);break;case IDENTIFIER:d.iterator=f=new Node(a,{type:IDENTIFIER}),f.name=f.value,d.varDecl=e=new Node(a,{type:VAR}),e.push(f),b.parentScript.varDecls.push(f);break;default:throw a.newSyntaxError("missing identifier")}a.mustMatch(IN),d.object=Expression(a,b),MaybeRightParen(a,g),c.push(d)}while(a.match(FOR));a.match(IF)&&(c.guard=HeadExpression(a,b));return c}function GeneratorExpression(a,b,c){return new Node(a,{type:GENERATOR,expression:c,tail:ComprehensionTail(a,b)})}function DestructuringExpression(a,b,c){var d=PrimaryExpression(a,b);d.destructuredNames=checkDestructuring(a,b,d,c);return d}function checkDestructuring(a,b,c,d){if(c.type===ARRAY_COMP)throw a.newSyntaxError("Invalid array comprehension left-hand side");if(c.type===ARRAY_INIT||c.type===OBJECT_INIT){var e={},f,g,h,i,j,k=c.children;for(var l=0,m=k.length;l=0)throw a.newSyntaxError("More than one switch default");case CASE:f=new Node(a),j===DEFAULT?e.defaultIndex=e.cases.length:f.caseLabel=Expression(a,l,COLON);break;default:throw a.newSyntaxError("Invalid switch case")}a.mustMatch(COLON),f.statements=new Node(a,blockInit());while((j=a.peek(!0))!==CASE&&j!==DEFAULT&&j!==RIGHT_CURLY)f.statements.push(Statement(a,l));e.cases.push(f)}return e;case FOR:e=new Node(a,LOOP_INIT),a.match(IDENTIFIER)&&(a.token.value==="each"?e.isEach=!0:a.unget()),b.parenFreeMode||a.mustMatch(LEFT_PAREN),l=b.pushTarget(e).nest(NESTING_DEEP),m=b.update({inForLoopInit:!0}),(j=a.peek())!==SEMICOLON&&(j===VAR||j===CONST?(a.get(),f=Variables(a,m)):j===LET?(a.get(),a.peek()===LEFT_PAREN?f=LetBlock(a,m,!1):(m.parentBlock=e,e.varDecls=[],f=Variables(a,m))):f=Expression(a,m));if(f&&a.match(IN)){e.type=FOR_IN,e.object=Expression(a,m);if(f.type===VAR||f.type===LET){h=f.children;if(h.length!==1&&f.destructurings.length!==1)throw new SyntaxError("Invalid for..in left-hand side",a.filename,f.lineno);f.destructurings.length>0?e.iterator=f.destructurings[0]:e.iterator=h[0],e.varDecl=f}else{if(f.type===ARRAY_INIT||f.type===OBJECT_INIT)f.destructuredNames=checkDestructuring(a,m,f);e.iterator=f}}else{e.setup=f,a.mustMatch(SEMICOLON);if(e.isEach)throw a.newSyntaxError("Invalid for each..in loop");e.condition=a.peek()===SEMICOLON?null:Expression(a,m),a.mustMatch(SEMICOLON),k=a.peek(),e.update=(b.parenFreeMode?k===LEFT_CURLY||definitions.isStatementStartCode[k]:k===RIGHT_PAREN)?null:Expression(a,m)}b.parenFreeMode||a.mustMatch(RIGHT_PAREN),e.body=Statement(a,l);return e;case WHILE:e=new Node(a,{isLoop:!0}),e.condition=HeadExpression(a,b),e.body=Statement(a,b.pushTarget(e).nest(NESTING_DEEP));return e;case DO:e=new Node(a,{isLoop:!0}),e.body=Statement(a,b.pushTarget(e).nest(NESTING_DEEP)),a.mustMatch(WHILE),e.condition=HeadExpression(a,b);if(!b.ecmaStrictMode){a.match(SEMICOLON);return e}break;case BREAK:case CONTINUE:e=new Node(a),l=b.pushTarget(e),a.peekOnSameLine()===IDENTIFIER&&(a.get(),e.label=a.token.value),e.target=e.label?l.labeledTargets.find(function(a){return a.labels.has(e.label)}):l.defaultTarget;if(!e.target)throw a.newSyntaxError("Invalid "+(j===BREAK?"break":"continue"));if(!e.target.isLoop&&j===CONTINUE)throw a.newSyntaxError("Invalid continue");break;case TRY:e=new Node(a,{catchClauses:[]}),e.tryBlock=Block(a,b);while(a.match(CATCH)){f=new Node(a),g=MaybeLeftParen(a,b);switch(a.get()){case LEFT_BRACKET:case LEFT_CURLY:a.unget(),f.varName=DestructuringExpression(a,b,!0);break;case IDENTIFIER:f.varName=a.token.value;break;default:throw a.newSyntaxError("missing identifier in catch")}if(a.match(IF)){if(b.ecma3OnlyMode)throw a.newSyntaxError("Illegal catch guard");if(e.catchClauses.length&&!e.catchClauses.top().guard)throw a.newSyntaxError("Guarded catch after unguarded");f.guard=Expression(a,b)}MaybeRightParen(a,g),f.block=Block(a,b),e.catchClauses.push(f)}a.match(FINALLY)&&(e.finallyBlock=Block(a,b));if(!e.catchClauses.length&&!e.finallyBlock)throw a.newSyntaxError("Invalid try statement");return e;case CATCH:case FINALLY:throw a.newSyntaxError(definitions.tokens[j]+" without preceding try");case THROW:e=new Node(a),e.exception=Expression(a,b);break;case RETURN:e=ReturnOrYield(a,b);break;case WITH:e=new Node(a),e.object=HeadExpression(a,b),e.body=Statement(a,b.pushTarget(e).nest(NESTING_DEEP));return e;case VAR:case CONST:e=Variables(a,b);break;case LET:a.peek()===LEFT_PAREN?e=LetBlock(a,b,!0):e=Variables(a,b);break;case DEBUGGER:e=new Node(a);break;case NEWLINE:case SEMICOLON:e=new Node(a,{type:SEMICOLON}),e.expression=null;return e;default:if(j===IDENTIFIER){j=a.peek();if(j===COLON){d=a.token.value;if(b.allLabels.has(d))throw a.newSyntaxError("Duplicate label");a.get(),e=new Node(a,{type:LABEL,label:d}),e.statement=Statement(a,b.pushLabel(d).nest(NESTING_SHALLOW)),e.target=e.statement.type===LABEL?e.statement.target:e.statement;return e}}e=new Node(a,{type:SEMICOLON}),a.unget(),e.expression=Expression(a,b),e.end=e.expression.end}MagicalSemicolon(a);return e}function Block(a,b){a.mustMatch(LEFT_CURLY);var c=new Node(a,blockInit());Statements(a,b.update({parentBlock:c}).pushTarget(c),c),a.mustMatch(RIGHT_CURLY);return c}function Statements(a,b,c){try{while(!a.done&&a.peek(!0)!==RIGHT_CURLY)c.push(Statement(a,b))}catch(d){a.done&&(a.unexpectedEOF=!0);throw d}}function MaybeRightParen(a,b){b===LEFT_PAREN&&a.mustMatch(RIGHT_PAREN)}function MaybeLeftParen(a,b){if(b.parenFreeMode)return a.match(LEFT_PAREN)?LEFT_PAREN:END;return a.mustMatch(LEFT_PAREN).type}function scriptInit(){return{type:SCRIPT,funDecls:[],varDecls:[],modDecls:[],impDecls:[],expDecls:[],loadDeps:[],hasEmptyReturn:!1,hasReturnWithValue:!1,isGenerator:!1}}function blockInit(){return{type:BLOCK,varDecls:[]}}function tokenString(a){var b=definitions.tokens[a];return/^\W/.test(b)?definitions.opTypeNames[b]:b.toUpperCase()}function Node(a,b){var c=a.token;c?(this.type=c.type,this.value=c.value,this.lineno=c.lineno,this.start=c.start,this.end=c.end):this.lineno=a.lineno,this.tokenizer=a,this.children=[];for(var d in b)this[d]=b[d]}function Script(a,b){var c=new Node(a,scriptInit()),d=new StaticContext(c,c,b,!1,NESTING_TOP);Statements(a,d,c);return c}function StaticContext(a,b,c,d,e){this.parentScript=a,this.parentBlock=b,this.inFunction=c,this.inForLoopInit=d,this.nesting=e,this.allLabels=new Stack,this.currentLabels=new Stack,this.labeledTargets=new Stack,this.defaultTarget=null,definitions.options.ecma3OnlyMode&&(this.ecma3OnlyMode=!0),definitions.options.parenFreeMode&&(this.parenFreeMode=!0)}function pushDestructuringVarDecls(a,b){for(var c in a){var d=a[c];d.type===IDENTIFIER?b.varDecls.push(d):pushDestructuringVarDecls(d,b)}}var lexer=require("ace/narcissus/jslex"),definitions=require("ace/narcissus/jsdefs");const StringMap=definitions.StringMap,Stack=definitions.Stack;eval(definitions.consts);const NESTING_TOP=0,NESTING_SHALLOW=1,NESTING_DEEP=2;StaticContext.prototype={ecma3OnlyMode:!1,parenFreeMode:!1,update:function(a){var b={};for(var c in a)b[c]={value:a[c],writable:!0,enumerable:!0,configurable:!0};return Object.create(this,b)},pushLabel:function(a){return this.update({currentLabels:this.currentLabels.push(a),allLabels:this.allLabels.push(a)})},pushTarget:function(a){var b=a.isLoop||a.type===SWITCH;if(this.currentLabels.isEmpty())return b?this.update({defaultTarget:a}):this;a.labels=new StringMap,this.currentLabels.forEach(function(b){a.labels.set(b,!0)});return this.update({currentLabels:new Stack,labeledTargets:this.labeledTargets.push(a),defaultTarget:b?a:this.defaultTarget})},nest:function(a){var b=Math.max(this.nesting,a);return b!==this.nesting?this.update({nesting:b}):this}},definitions.defineProperty(Array.prototype,"top",function(){return this.length&&this[this.length-1]},!1,!1,!0);var Np=Node.prototype={};Np.constructor=Node,Np.toSource=Object.prototype.toSource,Np.push=function(a){a!==null&&(a.start=0)b+=c;return b},!1,!1,!0);const DECLARED_FORM=0,EXPRESSED_FORM=1,STATEMENT_FORM=2;exports.parse=parse,exports.parseStdin=parseStdin,exports.Node=Node,exports.DECLARED_FORM=DECLARED_FORM,exports.EXPRESSED_FORM=EXPRESSED_FORM,exports.STATEMENT_FORM=STATEMENT_FORM,exports.Tokenizer=lexer.Tokenizer,exports.FunctionDefinition=FunctionDefinition}),define("ace/narcissus/jslex",function(require,exports,module){function Tokenizer(a,b,c){this.cursor=0,this.source=String(a),this.tokens=[],this.tokenIndex=0,this.lookahead=0,this.scanNewlines=!1,this.unexpectedEOF=!1,this.filename=b||"",this.lineno=c||1}var definitions=require("ace/narcissus/jsdefs");eval(definitions.consts);var opTokens={};for(var op in definitions.opTypeNames){if(op==="\n"||op===".")continue;var node=opTokens;for(var i=0;i"9")throw this.newSyntaxError("Missing exponent");do ch=a[this.cursor++];while(ch>="0"&&ch<="9");this.cursor--;return!0}return!1},lexZeroNumber:function(a){var b=this.token,c=this.source;b.type=NUMBER,a=c[this.cursor++];if(a==="."){do a=c[this.cursor++];while(a>="0"&&a<="9");this.cursor--,this.lexExponent(),b.value=parseFloat(b.start,this.cursor)}else if(a==="x"||a==="X"){do a=c[this.cursor++];while(a>="0"&&a<="9"||a>="a"&&a<="f"||a>="A"&&a<="F");this.cursor--,b.value=parseInt(c.substring(b.start,this.cursor))}else if(a>="0"&&a<="7"){do a=c[this.cursor++];while(a>="0"&&a<="7");this.cursor--,b.value=parseInt(c.substring(b.start,this.cursor))}else this.cursor--,this.lexExponent(),b.value=0},lexNumber:function(a){var b=this.token,c=this.source;b.type=NUMBER;var d=!1;do a=c[this.cursor++],a==="."&&!d&&(d=!0,a=c[this.cursor++]);while(a>="0"&&a<="9");this.cursor--;var e=this.lexExponent();d=d||e;var f=c.substring(b.start,this.cursor);b.value=d?parseFloat(f):parseInt(f)},lexDot:function(a){var b=this.token,c=this.source,d=c[this.cursor];if(d>="0"&&d<="9"){do a=c[this.cursor++];while(a>="0"&&a<="9");this.cursor--,this.lexExponent(),b.type=NUMBER,b.value=parseFloat(b.start,this.cursor)}else b.type=DOT,b.assignOp=null,b.value="."},lexString:function(ch){var token=this.token,input=this.source;token.type=STRING;var hasEscapes=!1,delim=ch;while((ch=input[this.cursor++])!==delim){if(this.cursor==input.length)throw this.newSyntaxError("Unterminated string literal");if(ch==="\\"){hasEscapes=!0;if(++this.cursor==input.length)throw this.newSyntaxError("Unterminated string literal")}}token.value=hasEscapes?eval(input.substring(token.start,this.cursor)):input.substring(token.start+1,this.cursor-1)},lexRegExp:function(ch){var token=this.token,input=this.source;token.type=REGEXP;do{ch=input[this.cursor++];if(ch==="\\")this.cursor++;else if(ch==="["){do{if(ch===undefined)throw this.newSyntaxError("Unterminated character class");ch==="\\"&&this.cursor++,ch=input[this.cursor++]}while(ch!=="]")}else if(ch===undefined)throw this.newSyntaxError("Unterminated regex")}while(ch!=="/");do ch=input[this.cursor++];while(ch>="a"&&ch<="z");this.cursor--,token.value=eval(input.substring(token.start,this.cursor))},lexOp:function(a){var b=this.token,c=this.source,d=opTokens[a],e=c[this.cursor];e in d&&(d=d[e],this.cursor++,e=c[this.cursor],e in d&&(d=d[e],this.cursor++,e=c[this.cursor]));var f=d.op;definitions.assignOps[f]&&c[this.cursor]==="="?(this.cursor++,b.type=ASSIGN,b.assignOp=definitions.tokenIds[definitions.opTypeNames[f]],f+="="):(b.type=definitions.tokenIds[definitions.opTypeNames[f]],b.assignOp=null),b.value=f},lexIdent:function(a){var b=this.token,c=this.source;do a=c[this.cursor++];while(a>="a"&&a<="z"||a>="A"&&a<="Z"||a>="0"&&a<="9"||a==="$"||a==="_");this.cursor--;var d=c.substring(b.start,this.cursor);b.type=definitions.keywords[d]||IDENTIFIER,b.value=d},get:function(a){var b;while(this.lookahead){--this.lookahead,this.tokenIndex=this.tokenIndex+1&3,b=this.tokens[this.tokenIndex];if(b.type!==NEWLINE||this.scanNewlines)return b.type}this.skip(),this.tokenIndex=this.tokenIndex+1&3,b=this.tokens[this.tokenIndex],b||(this.tokens[this.tokenIndex]=b={});var c=this.source;if(this.cursor===c.length)return b.type=END;b.start=this.cursor,b.lineno=this.lineno;var d=c[this.cursor++];if(d>="a"&&d<="z"||d>="A"&&d<="Z"||d==="$"||d==="_")this.lexIdent(d);else if(a&&d==="/")this.lexRegExp(d);else if(d in opTokens)this.lexOp(d);else if(d===".")this.lexDot(d);else if(d>="1"&&d<="9")this.lexNumber(d);else if(d==="0")this.lexZeroNumber(d);else if(d==='"'||d==="'")this.lexString(d);else if(this.scanNewlines&&d==="\n")b.type=NEWLINE,b.value="\n",this.lineno++;else throw this.newSyntaxError("Illegal token");b.end=this.cursor;return b.type},unget:function(){if(++this.lookahead===4)throw"PANIC: too much lookahead!";this.tokenIndex=this.tokenIndex-1&3},newSyntaxError:function(a){var b=new SyntaxError(a,this.filename,this.lineno);b.source=this.source,b.lineno=this.lineno,b.cursor=this.lookahead?this.tokens[this.tokenIndex+this.lookahead&3].start:this.cursor;return b}},exports.Tokenizer=Tokenizer}),define("ace/narcissus/jsdefs",function(a,b,c){function y(a){this.elts=a||null}function x(){this.table=Object.create(null,{}),this.size=0}function v(){return undefined}function u(a){return{getOwnPropertyDescriptor:function(b){var c=Object.getOwnPropertyDescriptor(a,b);c.configurable=!0;return c},getPropertyDescriptor:function(b){var c=s(a,b);c.configurable=!0;return c},getOwnPropertyNames:function(){return Object.getOwnPropertyNames(a)},defineProperty:function(b,c){Object.defineProperty(a,b,c)},"delete":function(b){return delete a[b]},fix:function(){if(Object.isFrozen(a))return t(a);return undefined},has:function(b){return b in a},hasOwn:function(b){return({}).hasOwnProperty.call(a,b)},get:function(b,c){return a[c]},set:function(b,c,d){a[c]=d;return!0},enumerate:function(){var b=[];for(m in a)b.push(m);return b},keys:function(){return Object.keys(a)}}}function t(a){var b={};for(var c in Object.getOwnPropertyNames(a))b[c]=Object.getOwnPropertyDescriptor(a,c);return b}function s(a,b){while(a){if(({}).hasOwnProperty.call(a,b))return Object.getOwnPropertyDescriptor(a,b);a=Object.getPrototypeOf(a)}}function r(a){return typeof a==="function"&&a.toString().match(/\[native code\]/)}function q(a,b,c,d,e,f){Object.defineProperty(a,b,{value:c,writable:!e,configurable:!d,enumerable:!f})}function p(a,b,c,d,e){Object.defineProperty(a,b,{get:c,configurable:!d,enumerable:!e})}b.options={version:185},function(){b.hostGlobal=this}();var d=["END","\n",";",",","=","?",":","CONDITIONAL","||","&&","|","^","&","==","!=","===","!==","<","<=",">=",">","<<",">>",">>>","+","-","*","/","%","!","~","UNARY_PLUS","UNARY_MINUS","++","--",".","[","]","{","}","(",")","SCRIPT","BLOCK","LABEL","FOR_IN","CALL","NEW_WITH_ARGS","INDEX","ARRAY_INIT","OBJECT_INIT","PROPERTY_INIT","GETTER","SETTER","GROUP","LIST","LET_BLOCK","ARRAY_COMP","GENERATOR","COMP_TAIL","IDENTIFIER","NUMBER","STRING","REGEXP","break","case","catch","const","continue","debugger","default","delete","do","else","false","finally","for","function","if","in","instanceof","let","new","null","return","switch","this","throw","true","try","typeof","var","void","yield","while","with"],e=["break","const","continue","debugger","do","for","if","return","switch","throw","try","var","yield","while","with"],f={"\n":"NEWLINE",";":"SEMICOLON",",":"COMMA","?":"HOOK",":":"COLON","||":"OR","&&":"AND","|":"BITWISE_OR","^":"BITWISE_XOR","&":"BITWISE_AND","===":"STRICT_EQ","==":"EQ","=":"ASSIGN","!==":"STRICT_NE","!=":"NE","<<":"LSH","<=":"LE","<":"LT",">>>":"URSH",">>":"RSH",">=":"GE",">":"GT","++":"INCREMENT","--":"DECREMENT","+":"PLUS","-":"MINUS","*":"MUL","/":"DIV","%":"MOD","!":"NOT","~":"BITWISE_NOT",".":"DOT","[":"LEFT_BRACKET","]":"RIGHT_BRACKET","{":"LEFT_CURLY","}":"RIGHT_CURLY","(":"LEFT_PAREN",")":"RIGHT_PAREN"},g={"__proto__":null},h={},i="const ";for(var j=0,k=d.length;j0&&(i+=", ");var l=d[j],m;/^[a-z]/.test(l)?(m=l.toUpperCase(),g[l]=j):m=/^\W/.test(l)?f[l]:l,i+=m+" = "+j,h[m]=j,d[l]=j}i+=";";var n={"__proto__":null};for(j=0,k=e.length;j>",">>>","+","-","*","/","%"];for(j=0,k=o.length;j Dirigible pricelist
    Item Amount Each Subtotal
    Total:
    ================================================ FILE: dirigible/shared/static/dirigible/scripts/cell_editor.js ================================================ // Copyright (c) 2010 Resolver Systems Ltd, PythonAnywhere LLP // See LICENSE.md // editor_methods = { init: function(args) { this.$input = $("") .bind("keydown.nav", function(e) { if (e.keyCode === $.ui.keyCode.LEFT || e.keyCode === $.ui.keyCode.RIGHT) { e.stopImmediatePropagation(); } }) .appendTo(args.container); if (document.activeElement !== args.formula_bar[0] ) { this.$input.focus(); } this.column = args.column; this.$formula_bar = args.formula_bar; var that = this; var updating = false; this.updateFormulaBarIfChanged = function(new_value) { if (that.$formula_bar.val() !== new_value) { that.$formula_bar.val(new_value); } }; this._updateFormulaBar = function() { if (updating) { return; } updating = true; that.updateFormulaBarIfChanged(that.$input.val()); updating = false; }; this.$input.bind("input", this._updateFormulaBar); this.$input.bind("propertychange", this._updateFormulaBar); this.updateInput = function() { if (updating) { return; } updating = true; that.$input.val(that.$formula_bar.val()); updating = false; }; this.$formula_bar.bind("input", this.updateInput); this.$formula_bar.bind("propertychange", this.updateInput); this.forwardReturnsToEditor = function(evt) { if (evt.which == 13) { var refirableEvent = $.Event("keydown"); refirableEvent.which = 13; that.$input.trigger(refirableEvent); } }; this.$formula_bar.bind("keydown", this.forwardReturnsToEditor); }, destroy: function () { this.$input.remove(); this.$formula_bar.unbind("input", this.updateInput); this.$formula_bar.unbind("propertychange", this.updateInput); this.$formula_bar.unbind("keydown", this.forwardReturnsToEditor); }, loadValue: function (gridRow) { var newValue = ""; if (gridRow !== undefined && gridRow[this.column.field] !== undefined) { newValue = gridRow[this.column.field].formula || ""; } this.defaultValue = newValue; this.setInputElementValues(newValue); this.$input[0].defaultValue = newValue; }, setInputElementValues: function(value) { this.$input.val(value); this.$input.caret(value.length, value.length); this.updateFormulaBarIfChanged(value); }, serializeValue: function () { return {formula: this.$input.val()}; }, applyValue: function (item, state) { item[this.column.field] = state; }, isValueChanged: function () { return ( !(this.$input.val() === "" && this.defaultValue === null) && this.$input.val() !== this.defaultValue ); }, validate: function () { return { valid: true, msg: null }; } }; function CellEditorFactory(currying_formula_bar) { return function(args) { $.extend(this, editor_methods); this.init($.extend({ formula_bar : currying_formula_bar }, args)); }; } ================================================ FILE: dirigible/shared/static/dirigible/scripts/console_view.js ================================================ // Copyright (c) 2010 Resolver Systems Ltd, PythonAnywhere LLP // See LICENSE.md // (function($) { $.extend(true, window, { "Dirigible": { "ConsoleView": ConsoleView } }); function ConsoleView($consoleDiv) { var self = this; self.updateMetaData = function(sheetMetaData) { if ('console_text' in sheetMetaData) { $consoleDiv.html(sheetMetaData['console_text']); } else { $consoleDiv.html(''); } }; } })(jQuery); ================================================ FILE: dirigible/shared/static/dirigible/scripts/dialogs.js ================================================ (function($) { $.extend(true, window, { "Dirigible": { "Dialogs": { "Initialise": Initialise } } }); function Initialise(grid, urls) { var _self = this; _self.grid = grid; $('#id_import_form_file').change(function() { if($('#id_import_form_file').val().match(/\.xls$/)) { $('#id_import_form_upload_xls_values_button').show(); $('#id_import_form_upload_csv_button').hide(); $('#id_import_form span.import_form_radio_button').hide(); $('#id_import_form form').attr('action', urls.importXls); } else { $('#id_import_form span.import_form_radio_button').show(); $('#id_import_form_upload_csv_button').show(); $('#id_import_form_upload_xls_values_button').hide(); $('#id_import_form form').attr('action', urls.importCsv); } }); $('#id_import_form_cancel_button').click( function() { $('#id_import_form').dialog("close"); }); $('#id_export_dialog_close_button').click( function() { $('#id_export_dialog').dialog("close"); }); $('#id_import_button').click(importButtonClickHandler); $('#id_export_button').click(function() {$('#id_export_dialog').dialog({width: 450});}); function importButtonClickHandler() { $('#id_import_form form')[0].reset(); var column = 1; var row = 1; var gridActiveCell = _self.grid.getActiveCell(); if (gridActiveCell) { column = gridActiveCell.cell; row = gridActiveCell.row + 1; $('#id_import_form_column').val(column); $('#id_import_form_row').val(row); } $('#id_import_form_upload_csv_button').hide(); $('#id_import_form span.import_form_radio_button').hide(); $('#id_import_form_upload_xls_values_button').hide(); $('#id_import_form').dialog(); } } })(jQuery); ================================================ FILE: dirigible/shared/static/dirigible/scripts/editor_commands.js ================================================ // Copyright (c) 2010 Resolver Systems Ltd, PythonAnywhere LLP // See LICENSE.md // (function($) { // register namespace $.extend(true, window, { "Dirigible": { "EditorCommands": EditorCommands } }); function EditorCommands(urls, editor) { function saveUsercode() { if (!editor.usercodeDirty) { return; } Dirigible.SheetUtils.abortOtherRecalculations(); var text = editor.getSession().getValue(); $.post( urls.setSheetUsercode, { usercode: text }, Dirigible.SheetUtils.queueRecalculation ); editor.usercodeDirty = false; } $.extend( this, { 'saveUsercode': saveUsercode } ); } })(jQuery); ================================================ FILE: dirigible/shared/static/dirigible/scripts/feedback_dialog.js ================================================ (function($) { $.extend(true, window, { "Dirigible": { "FeedbackDialog": { "Initialise": Initialise } } }); function Initialise(urls, username) { var _self = this; var default_email = "Email address (optional - only necessary if you would like us to contact you)"; if (username !== '') { $('#id_feedback_dialog_email_address').hide(); } else { $('#id_feedback_dialog_email_address').val(default_email); $('#id_feedback_dialog_email_address').addClass('email_prompt'); $('#id_feedback_dialog_email_address').focus(function() { $('#id_feedback_dialog_email_address').removeClass('email_prompt'); if ($('#id_feedback_dialog_email_address').val() == default_email) { $('#id_feedback_dialog_email_address').val(''); } }); } $('.feedback_link').click(function(event) { $('#id_feedback_dialog_error').addClass('hidden'); $('#id_feedback_dialog_spinner').addClass('hidden'); $('#id_feedback_dialog_ok_button').removeAttr('disabled'); $('#id_feedback_dialog_cancel_button').removeAttr('disabled'); $('#id_feedback_dialog').dialog({ width: 600 }); event.preventDefault(); }); $('#id_feedback_dialog_ok_button').click(function() { $('#id_feedback_dialog_spinner').removeClass('hidden'); $('#id_feedback_dialog_ok_button').attr('disabled', 'disabled'); $('#id_feedback_dialog_cancel_button').attr('disabled', 'disabled'); $('#id_feedback_dialog_error').addClass('hidden'); $.ajax({ type:'POST', url: urls.feedback, data: { message: $('#id_feedback_dialog_text').val(), email_address: $('#id_feedback_dialog_email_address').val(), username: username }, error: function() { $('#id_feedback_dialog_error').removeClass('hidden'); $('#id_feedback_dialog_ok_button').removeAttr('disabled'); $('#id_feedback_dialog_cancel_button').removeAttr('disabled'); $('#id_feedback_dialog_spinner').addClass('hidden'); }, success: function() { $('#id_feedback_dialog').dialog("close"); } }); }); $('#id_feedback_dialog_cancel_button').click(function() { $('#id_feedback_dialog_error').addClass('hidden'); $('#id_feedback_dialog').dialog("close"); }); } })(jQuery); ================================================ FILE: dirigible/shared/static/dirigible/scripts/grid_commands.js ================================================ // Copyright (c) 2010 Resolver Systems Ltd, PythonAnywhere LLP // See LICENSE.md // (function($) { // register namespace $.extend(true, window, { "Dirigible": { "GridCommands": GridCommands } }); function GridCommands(urls, grid) { var self = this; self.urls = urls; self.getPostFunction__ = function (name, recalculate) { var callback = undefined; if (recalculate) { callback = Dirigible.SheetUtils.queueRecalculation; } return function (startCol, startRow, endCol, endRow) { $.post( self.urls[name], {range: startCol+','+startRow+','+endCol+','+endRow}, callback ); }; }; self.sendCopyToServer__ = self.getPostFunction__('copy', false); self.sendCutToServer__ = self.getPostFunction__('cut', true); self.sendPasteToServer__ =self.getPostFunction__('paste', true); self.sendClearToServer__ = self.getPostFunction__('clearCells', true); self.copy = Dirigible.GridCommands.createSelectionCommand__( grid, self.sendCopyToServer__); self.cut = Dirigible.GridCommands.createSelectionCommand__( grid, self.sendCutToServer__); self.paste = Dirigible.GridCommands.createSelectionCommand__( grid, self.sendPasteToServer__); self.clear = Dirigible.GridCommands.createSelectionCommand__( grid, self.sendClearToServer__); self.resizeColumns = function(gridColumns) { var columnWidths = self.convertGridColumnsToJSON__(grid.getColumns()); $.post(urls.setColumnWidths, {column_widths: columnWidths}); }; self.convertGridColumnsToJSON__ = function(gridColumns) { var columnWidths = {}; for (var i=0; i < gridColumns.length; i++) { var col = gridColumns[i]; if (col.previousWidth != col.width) { columnWidths[String(i)] = col.width; } } return JSON.stringify(columnWidths); }; self.sendCellChangeToServer = function(event, gridLocation, retries) { Dirigible.SheetUtils.abortOtherRecalculations(); retries = (retries === undefined) ? 5 : retries; $.post( urls.setCellFormula, slickGridCellDataToPostParams(grid, gridLocation), function(response) { if (response === 'FAILED' && retries > 0) { self.sendCellChangeToServer( event, gridLocation, retries - 1 ); } else { self.setCellFormulaAndUpdateSheet__(gridLocation); } } ); }; self.setCellFormulaAndUpdateSheet__ = function (gridLocation) { grid.updateCell(gridLocation.row, gridLocation.column); Dirigible.SheetUtils.queueRecalculation(); }; self.commitCurrentEdit = function () { grid.getEditController().commitCurrentEdit(); } } Dirigible.GridCommands.createSelectionCommand__ = function(grid, command) { return function() { if (grid.getCellEditor() === null) { var selectedRange = ( grid.getSelectionModel().getSelectedRanges()[0] ); command( selectedRange.fromCell, selectedRange.fromRow + 1, selectedRange.toCell, selectedRange.toRow + 1 ); return true; } return false; }; }; })(jQuery); ================================================ FILE: dirigible/shared/static/dirigible/scripts/grid_content_converter.js ================================================ // Copyright (c) 2010 Resolver Systems Ltd, PythonAnywhere LLP // See LICENSE.md // function getGridCellFormula(grid, slickCellLocation) { var columnField = grid.getColumns()[slickCellLocation.cell].field; var rowData = grid.getDataItem(slickCellLocation.row); if (rowData !== undefined) { var cellData = rowData[columnField]; if (cellData !== undefined && cellData.formula !== undefined) { return cellData.formula; } } return ""; } function slickGridCellDataToPostParams(grid, slickCellLocation) { return { column: slickCellLocation.cell, row: slickCellLocation.row + 1, formula: grid.getCellFormula(slickCellLocation) }; } ================================================ FILE: dirigible/shared/static/dirigible/scripts/grid_interaction_handler.js ================================================ (function($) { // register namespace $.extend(true, window, { "Dirigible": { "GridInteractionHandler": GridInteractionHandler } }); function GridInteractionHandler($, grid, gridCommands, gridRemoteModel) { var self = this; self.handleKeyDown = function(event) { var TAB = 9; var DEL = 46; var BACKSPACE = 8; var LEFT = 37; var UP = 38; var PAGE_DOWN = 34; var PAGE_UP = 33; var HOME = 36; var END = 35; var LETTER_C = 67; var LETTER_V = 86; var LETTER_X = 88; var F2 = 113; if (event.ctrlKey || event.metaKey) { switch (event.which) { case LETTER_X: if (gridCommands.cut()) { event.stopImmediatePropagation(); } break; case LETTER_C: if (gridCommands.copy()) { event.stopImmediatePropagation(); } break; case LETTER_V: if (gridCommands.paste()) { event.stopImmediatePropagation(); } break; case HOME: grid.gotoCell(0, 1); event.stopImmediatePropagation(); break; case END: grid.gotoCell(grid.getDataLength() - 1, grid.getColumns().length - 1); event.stopImmediatePropagation(); break; } } else { switch (event.which) { case BACKSPACE: case DEL: if (gridCommands.clear()) { event.stopImmediatePropagation(); } break; case LEFT: if (grid.getActiveCell().cell === 1) { event.stopImmediatePropagation(); } break; case UP: if (grid.getActiveCell().row === 0) { event.stopImmediatePropagation(); } break; case PAGE_DOWN: var viewport = grid.getViewport(); var newTopRow = viewport.bottom - 1; var activeCell = grid.getActiveCell(); var pageSize = viewport.bottom - viewport.top - 1; if (viewport.bottom < grid.getDataLength()) { grid.scrollRowIntoView(newTopRow, true); } grid.gotoCell(activeCell.row + pageSize, activeCell.cell); event.stopImmediatePropagation(); break; case PAGE_UP: var viewport = grid.getViewport(); var activeCell = grid.getActiveCell(); var pageSize = viewport.bottom - viewport.top - 1; var newTopRow = viewport.top - pageSize; if (newTopRow < 0) { newTopRow = 0; } grid.scrollRowIntoView(newTopRow, false); grid.gotoCell(activeCell.row - pageSize, activeCell.cell); event.stopImmediatePropagation(); break; case F2: grid.editActiveCell(); break; } } }; self.handleGridDivKeyPress = function(event) { if (event.ctrlKey || event.altKey) { return true; } if (event.which > 47 || event.which === 32) { if (grid.getCellEditor() === null ) { grid.editActiveCell(); grid.getCellEditor().setInputElementValues(String.fromCharCode(event.which)); return false; } } return true; }; self.updateFormulaBar = function() { var activeCell = grid.getActiveCell(); if (!activeCell) { return; } $('#id_formula_bar').val(grid.getCellFormula(activeCell)); }; self.handleFormulaBarClick = function () { if (grid.getCellEditor() === null) { grid.editActiveCell(); } var $formulaBar = $('#id_formula_bar'); var caret = $formulaBar.caret(); $formulaBar.focus(); $formulaBar.caret(caret.start, caret.end); }; self.bind = function() { grid.onKeyDown.subscribe(self.handleKeyDown); $('#id_grid').keypress(self.handleGridDivKeyPress); $('#id_formula_bar').click(self.handleFormulaBarClick); grid.onColumnsResized.subscribe(gridCommands.resizeColumns); grid.onCellChange.subscribe(gridCommands.sendCellChangeToServer); grid.onActiveCellChanged.subscribe(self.updateFormulaBar); grid.onClick.subscribe(self.updateFormulaBar); }; } })(jQuery); ================================================ FILE: dirigible/shared/static/dirigible/scripts/grid_remote_model.js ================================================ (function($) { $.extend(true, window, { "Dirigible": { "GridRemoteModel": GridRemoteModel } }); function GridRemoteModel(urls) { var self = this; self.data = []; self.url = urls.getJSONGridData; self.patches = {}; self.pending = {}; self.patchWidth = 26; self.patchHeight = 100; self.getLength = function() { return self.data.length; }; self.getItem = function(index) { return self.data[index]; }; self.cellToPatch = function(col, row) { return { patchCol: Math.floor( Math.max((col - 1), 0) / self.patchWidth ), patchRow: Math.floor( Math.max((row - 1), 0) / self.patchHeight ) }; }; self.patchToCells = function(patch) { return { left: patch.patchCol * self.patchWidth + 1, topmost: patch.patchRow * self.patchHeight + 1, right: patch.patchCol * self.patchWidth + self.patchWidth, bottom: patch.patchRow * self.patchHeight + self.patchHeight }; }; self.choosePatchesToLoad = function(left, topmost, right, bottom) { var patchesToLoad = []; var nw = self.cellToPatch(left, topmost); var se = self.cellToPatch(right, bottom); for (var patchCol = nw.patchCol; patchCol <= se.patchCol; patchCol++) { for (var patchRow = nw.patchRow; patchRow <= se.patchRow; patchRow++) { patchesToLoad.push( { patchCol: patchCol, patchRow: patchRow } ); } } return patchesToLoad; }; self.isPatchLoadedOrPending = function(patch) { if (self.patches[patch.patchCol] !== undefined) { if(self.patches[patch.patchCol][patch.patchRow] !== undefined){ return true; } } if (self.pending[patch.patchCol] !== undefined) { if(self.pending[patch.patchCol][patch.patchRow] !== undefined){ return true; } } return false; }; self.loadPatchIfNecessary = function(patch) { if (!self.isPatchLoadedOrPending(patch)) { var range = self.patchToCells(patch); if (self.pending[patch.patchCol] === undefined){ self.pending[patch.patchCol] = {}; } self.pending[patch.patchCol][patch.patchRow] = true; self.onDataLoading.notify(); self.getData(range.left, range.topmost, range.right, range.bottom, patch); } }; self.getData = function (left, topmost, right, bottom, patch) { var range = left + ', ' + topmost + ', ' + right + ', ' + bottom; $.getJSON(self.url, {'range' : range}, self.getSuccessHandlerForPatch(patch) ); }; self.ensureData = function(left, topmost, right, bottom) { var patchesToLoad = self.choosePatchesToLoad(left, topmost, right, bottom); for (var patch=0; patch expectedArea.left || jsonData.topmost > expectedArea.topmost || jsonData.right < expectedArea.right || jsonData.bottom < expectedArea.bottom){ return } if (self.patches[patch.patchCol] === undefined){ self.patches[patch.patchCol] = {}; } self.patches[patch.patchCol][patch.patchRow] = true; if (self.pending[patch.patchCol] !== undefined) { delete self.pending[patch.patchCol][patch.patchRow]; } }; }; self.onSuccess = function(jsonData) { self.addData(jsonData); self.onDataLoaded.notify({ left: jsonData.left, topmost: jsonData.topmost, right: jsonData.right, bottom: jsonData.bottom }); }; self.reset = function(newData) { self.data = newData; self.patches = {}; self.pending = {}; }; } })(jQuery); ================================================ FILE: dirigible/shared/static/dirigible/scripts/grid_view.js ================================================ // Copyright (c) 2010 Resolver Systems Ltd, PythonAnywhere LLP // See LICENSE.md // (function($) { $.extend(true, window, { "Dirigible": { "GridView": GridView } }); var editor_locator = 'input.editor-text'; var formula_bar_locator = '#id_formula_bar'; function GridView(grid, remoteModel) { var self = this; self.grid = grid; self.remoteModel = remoteModel; self.with_restore_cursor = function( wrapped_function ) { var f = function() { var current_selected_editor_locator = null; var caret = null; if (document.activeElement == $(editor_locator)[0]) { current_selected_editor_locator = editor_locator; caret = $(current_selected_editor_locator).caret(); } else if (document.activeElement == $(formula_bar_locator)[0]) { current_selected_editor_locator = formula_bar_locator; caret = $(current_selected_editor_locator).caret(); } var contents = $(editor_locator).val(); wrapped_function.apply(null, arguments); if (current_selected_editor_locator !== null) { self.grid.editActiveCell(); } $(editor_locator).val(contents); $(formula_bar_locator).val(contents); if (current_selected_editor_locator !== null) { $(current_selected_editor_locator).focus(); $(current_selected_editor_locator).caret( caret.start, caret.end ); } }; f.wrapped = true; // for testing return f }; self.updateMetaData = self.with_restore_cursor( function(sheetMetaData) { var rows = Dirigible.GridView.jsonToSlickGridRowHeaders(sheetMetaData); self.remoteModel.reset(rows); self.grid.render(); self.ensureCurrentViewportData(); } ); self.ensureCurrentViewportData = function() { var $viewport = $('div.slick-viewport'); var topLeft = grid.getCellFromPoint( $viewport.scrollLeft(), $viewport.scrollTop() ); var bottomRight = grid.getCellFromPoint( $viewport.scrollLeft() + $viewport.width(), $viewport.scrollTop() + $viewport.height() ); self.remoteModel.ensureData( topLeft.cell, topLeft.row, bottomRight.cell, bottomRight.row ); }; self.handleOnDataLoading = function() { $('#id_buffering_message').removeClass('hidden'); }; self.remoteModel.onDataLoading.subscribe(self.handleOnDataLoading); self.handleOnDataLoaded = self.with_restore_cursor( function(event, loadedRange) { for (var row=loadedRange.topmost; row <= loadedRange.bottom; row++) { grid.invalidateRow(row - 1); } grid.updateRowCount(); grid.render(); $('#id_buffering_message').addClass('hidden'); } ); self.remoteModel.onDataLoaded.subscribe(self.handleOnDataLoaded); self.grid.onScroll.subscribe(self.ensureCurrentViewportData); }; GridView.jsonToSlickGridData = function(jsonData) { var slickgrid_sheet = []; for (i=0; i < jsonData.height; i++) { slickgrid_sheet[i] = jQuery.extend( { header: i + 1 }, jsonData[i + 1] ); } return slickgrid_sheet; }; GridView.jsonToSlickGridRowHeaders = function(jsonData) { var height = jsonData.height || 1000; var slickgrid_sheet = []; for (var ii = 1; ii <= height; ii++) { slickgrid_sheet.push({header: ii}); } return slickgrid_sheet; } GridView.jsonToSlickGridColumnHeaders = function(jsonData) { var width = jsonData.width || 52; var sheet_columns = [{ name: "", field: "header", id: "header", width: 40, sortable: false, unselectable: true }]; for (var ii = 1; ii <= width; ii++) { var column_name = GridView.columnIndexToName(ii); var fieldID = "" + ii; var column_options = { name: column_name, field: fieldID, id: fieldID, formatter: GridView.cellFormatter}; if (jsonData.column_widths && jsonData.column_widths[ii]) { column_options.width = jsonData.column_widths[ii]; } sheet_columns.push(column_options); } return sheet_columns; }; GridView.columnIndexToName = function(index) { var columnName = ''; while (index > 0) { var thisChar = String.fromCharCode((index - 1) % 26 + 65); index = Math.floor((index - 1) / 26); columnName = thisChar + columnName; } return columnName; }; GridView.cellFormatter = function(row, column, cell, columnDef, dataContext) { if (cell !== undefined && cell.error !== undefined) { row = row + 1; return ""; } if (cell === undefined) { return ""; } if (cell.formatted_value === undefined) { if (cell.formula) { var escaped_formula = htmlescape(cell.formula); return ( '' + escaped_formula + "" ); } else { return ""; } } var escaped_formatted_value = htmlescape(cell.formatted_value); return ( '' + escaped_formatted_value + "" ); }; })(jQuery); ================================================ FILE: dirigible/shared/static/dirigible/scripts/htmlescape.js ================================================ // Copyright (c) 2010 Resolver Systems Ltd, PythonAnywhere LLP // See LICENSE.md // function htmlescape(untrusted_string) { var better_string = untrusted_string.replace(/&/g, '&'); better_string = better_string.replace(/>/g, '>'); better_string = better_string.replace(/ ', after_save: pageView.updatePageTitle } ); } $("#id_grid_and_code").splitter({ splitVertical: true, outline: true, anchorToWindow: true, sizeLeft: true, cookie: 'dirigible_vertical_splitter' }); $("#id_right_column").splitter({ splitHorizontal: true, outline: true, anchorToWindow: true, sizeBottom: true, cookie: 'dirigible_horizontal_splitter' }); }; self.resizeComponents = function(event) { $('#id_console').width($('#id_right_column').width() - 20); $('#id_console').height($('#id_console_wrap').height() - 20); var gridAndFormulaBarHeight = $('#id_left_column').height() - 10; $('#id_grid_and_formula_bar').height(gridAndFormulaBarHeight); $('#id_grid').height(gridAndFormulaBarHeight - $('#id_formula_bar').height() - 18); $('#id_formula_bar').width($('#id_grid').width() - 8); grid.resizeCanvas(); if ($('#id_usercode_wrap').height() < 30) { $('#id_usercode').hide(); } else { $('#id_usercode').show(); } $('#id_usercode').width($('#id_usercode_wrap').width() - 20); $('#id_usercode').height($('#id_usercode_wrap').height() - 10); editor.resize(); }; self.bind = function() { // Click-away handlers editor.addEventListener('click', self.commitCurrentEdit); $('#id_right_column, #id_header_and_toolbar').bind( 'click', self.commitCurrentEdit ); editor.addEventListener('blur', editorCommands.saveUsercode); editor.usercodeDirty = false; editor.getSession().addEventListener( 'change', function() { editor.usercodeDirty = true; }); // Ctrl-S always saves usercode. // and F9 recalculates $(document).keydown( function(event) { if (event.ctrlKey && event.which === 83) { editorCommands.saveUsercode(); event.preventDefault(); } else if (event.which === 120) { pageCommands.recalculate(); event.preventDefault(); } } ); $(document).mouseup(function() { window.setTimeout(self.resizeComponents, 0); }); $(window).resize(function(event) { window.setTimeout(self.resizeComponents, 0); }); }; } })(jQuery); ================================================ FILE: dirigible/shared/static/dirigible/scripts/page_view.js ================================================ // Copyright (c) 2010 Resolver Systems Ltd, PythonAnywhere LLP // See LICENSE.md // (function($) { $.extend(true, window, { "Dirigible": { "PageView": PageView } }); function PageView(username, consoleView, usercodeView, gridView) { var self = this; self.updateMetaData = function(sheetMetaData) { if (sheetMetaData === null || sheetMetaData === undefined) { return; } consoleView.updateMetaData(sheetMetaData); usercodeView.updateMetaData(sheetMetaData); gridView.updateMetaData(sheetMetaData); $('#id_sheet_name').text(sheetMetaData.name); self.updatePageTitle(); }; self.updatePageTitle = function() { document.title = username + "'s " + $('#id_sheet_name').text() + ": Dirigible"; }; } })(jQuery); ================================================ FILE: dirigible/shared/static/dirigible/scripts/security_settings.js ================================================ // Copyright (c) 2010 Resolver Systems Ltd, PythonAnywhere LLP // See LICENSE.md // (function($) { $.extend(true, window, { "Dirigible": { "SecuritySettings": { "Dialog" : Dialog, "Model" : Model } } }); function Model($, urls) { var self = this; self.updatePageUI = function() { var securityButton = $('#id_security_button'); var title_text = 'Sheet security settings ('; if (self.isPublic) { title_text += 'Sheet is public. '; } else { title_text += 'Sheet is private. '; } if (self.enabled) { title_text += 'JSON API enabled)'; } else { title_text += 'JSON API disabled)'; } securityButton.attr('alt', title_text); securityButton.attr('title', title_text); }; self.updateServerState = function( publicSheetCheckboxState, jsonAPICheckboxState, jsonAPIKeyFieldValue, successHandler, errorHandler ) { $.ajax({ type: "POST", url: urls.setSecuritySettings, data: { 'is_public': publicSheetCheckboxState, 'allow_json_api_access': jsonAPICheckboxState, 'api_key': jsonAPIKeyFieldValue }, success: function(data) { self.handleServerUpdateResponse( data, publicSheetCheckboxState, jsonAPICheckboxState, jsonAPIKeyFieldValue, successHandler, errorHandler ); }, error: errorHandler }); }; self.handleServerUpdateResponse = function(data, publicSheetCheckboxState, jsonAPICheckboxState, jsonAPIKeyFieldValue, successHandler, errorHandler) { if (data === 'OK') { successHandler(); self.isPublic = publicSheetCheckboxState; self.enabled = jsonAPICheckboxState; self.apiKey = jsonAPIKeyFieldValue; self.updatePageUI(); } else { errorHandler(); } }; } function Dialog($, urls) { var self = this; self.show = function(model) { self.model__ = model; $('#id_security_form_save_error').addClass('hidden'); self.updateDialogUI(model.isPublic, model.enabled, model.apiKey); $('#id_security_form').dialog({ width: 400 }); }; self.updateDialogUI = function(isPublic, enabled, apiKey) { $('#id_security_form_json_enabled_checkbox').attr('checked', enabled); $('#id_security_form_public_sheet_checkbox').attr('checked', isPublic); $apiKeyField = $('#id_security_form_json_api_key'); $apiUrlField = $('#id_security_form_json_api_url'); self.enableJsonApiFields_(enabled); $apiKeyField.val(apiKey); self.updateAPIURL(); }; self.enableJsonApiFields_ = function(enabled) { if (enabled){ $apiKeyField.attr('disabled', ''); $apiUrlField.attr('disabled', ''); } else { $apiKeyField.attr('disabled', 'disabled'); $apiUrlField.attr('disabled', 'disabled'); } }; self.updateAPIURL = function() { var newApiUrl = 'http://' + window.location.host + urls.apiBase + '?api_key=' + $('#id_security_form_json_api_key').val(); $('#id_security_form_json_api_url').val(newApiUrl); }; self.updateDialogUIFromCheckboxes = function() { var checkboxJSON = $('#id_security_form_json_enabled_checkbox'); var stateJSON = checkboxJSON.attr('checked'); var checkboxPublic = $('#id_security_form_public_sheet_checkbox'); var statePublic = checkboxPublic.attr('checked'); self.enableJsonApiFields_(stateJSON); }; self.onOK = function() { var publicCheckboxState = $('#id_security_form_public_sheet_checkbox').attr('checked'); var jsonAPICheckboxState = $('#id_security_form_json_enabled_checkbox').attr('checked'); var jsonAPIKeyFieldValue = $('#id_security_form_json_api_key').val(); self.model__.updateServerState( publicCheckboxState, jsonAPICheckboxState, jsonAPIKeyFieldValue, self.close, self.handleSaveError ); return false; }; self.close = function() { $('#id_security_form').dialog("close"); }; self.handleSaveError = function() { $('#id_security_form_save_error').removeClass('hidden'); }; self.selectFullURL_ = function() { $(this).caret(0, $(this).val().length); }; self.bind = function(model) { $('#id_security_form_ok_button').click(self.onOK); $('#id_security_form_cancel_button').click(self.close); $('#id_security_form_json_api_key').bind( 'keyup mouseup change select', self.updateAPIURL ); $('#id_security_form_json_enabled_checkbox').click(self.updateDialogUIFromCheckboxes); $('#id_security_form_json_api_url').click(self.selectFullURL_); $('#id_security_form_json_api_key').click(self.selectFullURL_); }; } })(jQuery); ================================================ FILE: dirigible/shared/static/dirigible/scripts/selection_model.js ================================================ // Copyright (c) 2010 Resolver Systems Ltd, PythonAnywhere LLP // See LICENSE.md // (function($) { // register namespace $.extend(true, window, { Dirigible: { SelectionModel: SelectionModel } }); function SelectionModel() { this.selectors = [ new Slick.CellRangeSelector(), new Dirigible.KeyboardCellRangeSelector() ]; this.onSelectedRangesChanged = new Slick.Event(); this.ranges = []; } SelectionModel.prototype.init = function(grid) { this.grid = grid; this.handleActiveCellChanged = this.getHandleActiveCellChanged(); this.grid.onActiveCellChanged.subscribe(this.handleActiveCellChanged); this.handleBeforeCellRangeSelected = this.getHandleBeforeCellRangeSelected(); this.handleCellRangeSelected = this.getHandleCellRangeSelected(); for (var i=0; i < this.selectors.length; i++) { var selector = this.selectors[i]; this.grid.registerPlugin(selector); selector.onBeforeCellRangeSelected.subscribe(this.handleBeforeCellRangeSelected); selector.onCellRangeSelected.subscribe(this.handleCellRangeSelected); } }; SelectionModel.prototype.destroy = function() { this.grid.onActiveCellChanged.unsubscribe(this.handleActiveCellChanged); for (var i=0; i < this.selectors.length; i++) { var selector = this.selectors[i]; selector.onBeforeCellRangeSelected.unsubscribe(this.handleBeforeCellRangeSelected); selector.onCellRangeSelected.unsubscribe(this.handleCellRangeSelected); this.grid.unregisterPlugin(selector); } }; SelectionModel.prototype.getSelectedRanges = function() { return this.ranges; }; SelectionModel.prototype.setSelectedRanges = function(ranges) { this.ranges = ranges; this.onSelectedRangesChanged.notify(this.ranges); }; SelectionModel.prototype.getHandleActiveCellChanged = function() { var _self = this; return function(event, cell) { var activeCell = _self.grid.getActiveCell(); _self.setSelectedRanges([new Slick.Range( activeCell.row, activeCell.cell, activeCell.row, activeCell.cell)]); }; }; SelectionModel.prototype.getHandleBeforeCellRangeSelected = function() { var _self = this; return function(event, cell) { if (_self.grid.canCellBeSelected(cell.row, cell.cell)) { _self.grid.getEditController().commitCurrentEdit(); _self.grid.setActiveCell(cell.row, cell.cell); } return true; }; }; SelectionModel.prototype.getHandleCellRangeSelected = function() { var _self = this; return function(event, args) { _self.setSelectedRanges([args.range]); }; }; })(jQuery); ================================================ FILE: dirigible/shared/static/dirigible/scripts/sheet_page_utils.js ================================================ (function($) { // register namespace $.extend(true, window, { "Dirigible": { "SheetUtils": { "Initialise": InitialiseSheetUtils } } }); function InitialiseSheetUtils(urls, pageView) { var _self = this; function createNewGrid() { } function queueRecalculation() { $.ajaxq('recalculation_queue', { type: 'get', url: urls.calculate, success: getMetaData }); } function abortOtherRecalculations() { $.ajaxq('recalculation_queue'); } function getMetaData(content) { if (content === 'OK') { $.ajaxq('get_json_queue'); $.ajaxq('get_json_queue', { type: 'get', dataType: 'json', url: urls.getJSONMetaData, success: pageView.updateMetaData }); } } $.extend(true, window, { "Dirigible": { "SheetUtils": { "createNewGrid": createNewGrid, "queueRecalculation": queueRecalculation, "getMetaData": getMetaData, "abortOtherRecalculations": abortOtherRecalculations } } }); } })(jQuery); ================================================ FILE: dirigible/shared/static/dirigible/scripts/toolbar_interaction_handler.js ================================================ (function($) { // register namespace $.extend(true, window, { "Dirigible": { "ToolbarInteractionHandler": ToolbarInteractionHandler } }); function ToolbarInteractionHandler(gridCommands, pageCommands) { var self = this; self.bind = function() { $('#id_cut_button').click(gridCommands.cut); $('#id_copy_button').click(gridCommands.copy); $('#id_paste_button').click(gridCommands.paste); $('#id_recalc_button').click(pageCommands.recalculate); }; } })(jQuery); ================================================ FILE: dirigible/shared/static/dirigible/scripts/usercode_view.js ================================================ // Copyright (c) 2010 Resolver Systems Ltd, PythonAnywhere LLP // See LICENSE.md // (function($) { $.extend(true, window, { "Dirigible": { "UsercodeView": UsercodeView } }); function UsercodeView(editor) { var self = this; self.initialiseComponents = function(editable) { var PythonMode = require("ace/mode/python").Mode; editor.getSession().setMode(new PythonMode()); editor.setReadOnly(!editable); }; self.updateMetaData = function(sheetMetaData) { var annotations = []; if ('usercode_error' in sheetMetaData) { annotations.push( { row: parseInt(sheetMetaData.usercode_error.line) - 1, column: 0, text: sheetMetaData.usercode_error.message, type: "error" } ); } editor.getSession().setAnnotations(annotations); }; } })(jQuery); ================================================ FILE: dirigible/shared/static/dirigible/styles/base.css ================================================ html { height: 100%; } body { height: 100%; margin: 0; padding: 0; font-family: Helvetica, Arial, sans-serif; background: #009EE0; color: #FFFFFF; } a { text-decoration: None; color: #FFFFFF; } a:hover { text-decoration: underline; } .blue_button { color: #FFFFFF; background-color: #016AA1; border: 0px; margin: 0px; } input[type=submit], input[type=button], a.button { } a.button:hover { text-decoration: none; } a img { border: 0px; } /* Separators between (eg.) "My account" and "Log out" */ .separator { color: #5CBEE5; margin-left: 0.25em; margin-right: 0.25em; } div.clear { clear: both } .hidden { display: none; } .dialog_buttons { float: right; } #id_small_header_logo { margin: 15px 0px 8px 10px; float: left; } #id_top_links { float: right; clear: right; margin-top: 10px; margin-right: 16px; font-size: 14pt; font-weight: bold; } #id_account_buttons_wrap { padding-top: 14px; padding-bottom: 11px; float: right; background-color: #006AA0; font-weight: bold; padding-left: 10px; padding-right: 10px; } #id_feedback_dialog_blurb_big { font-weight: bold; font-size: larger; margin-bottom: 0.25em; margin-top: 0.25em; } #id_feedback_dialog_text { margin-top: 1em; margin-bottom: 1em; width: 562px; } #id_feedback_dialog_email_address { width: 562px; } .email_prompt { font-style: italic; color: #808080; } #id_feedback_dialog .dialog_buttons { margin-top: 1.5em; } #id_feedback_dialog_error { font-size: smaller; margin-top: 3ex; float: left; } #id_feedback_dialog_spinner { margin-right: 1ex; } #id_feedback_dialog_ok_button { font-weight: bold; } /* JQuery UI style overrides */ .ui-widget-content a { color: #009EE0 !important; } ================================================ FILE: dirigible/shared/static/dirigible/styles/coming_soon_page.css ================================================ #id_coming_soon { margin-top: 35px; margin-bottom: 35px; font-size: 28px; color: #4B576D; text-align: center; height: 700px; } ================================================ FILE: dirigible/shared/static/dirigible/styles/contact.css ================================================ p.address { margin-left: 2ex; } p.regulatory { clear: both; padding-top: 20px; font-size: smaller; } div.map { float: right; margin: 0 2em 0.5em 0.5em; padding: 0.3em; border: 1px solid lightgray; } ================================================ FILE: dirigible/shared/static/dirigible/styles/error.css ================================================ #id_error_wrap { width: 29em; clear: both; margin-top: 4em; margin-left: auto; margin-right: auto; padding-top: 1.5em; padding-bottom: 1.5em; font-size: 14pt; text-align: center; } #id_recovery_block a { color: #FFFFFF; font-weight: bold; } ================================================ FILE: dirigible/shared/static/dirigible/styles/index.css ================================================ .id_dirigible_tagline { font-size: 20px; color: #4B576D; text-align: center; } #id_grey_stripe_hwrap { background-color: #D1D2D4; padding-top: 40px; padding-bottom: 40px; } #id_more_info_buttons { /* Only easy to center a block if its width is fixed :-( Luckily this one contains images so we know how wide it is. */ width: 470px; margin-left: auto; margin-right: auto; margin-top: 35px; } #id_more_info_buttons a img { border: 0px; } #id_find_out_more { margin: 0px 5px 0px 0px; padding: 0px; } #id_watch_a_video { margin: 0px 0px 0px 5px; padding: 0px; } #id_featured_sheets_front_page { margin-top: 30px; } #id_featured_sheets_front_page a { color: #4B576D; text-decoration: underline; } #id_signup_call_to_action_div { font-size: 36pt; margin-top: 35px; text-align: center; width: 11.5ex; border-radius: 24px; -moz-border-radius: 24px; } #id_signup_call_to_action { } #id_footer_links { position: relative; bottom: 0; } ================================================ FILE: dirigible/shared/static/dirigible/styles/info_page.css ================================================ #id_extra_banner_spacing { clear: both; margin-bottom: 10px; } h1 { padding-top: 0px; margin-top: 0px; } #id_main_background { padding-bottom: 0px; padding-top: 0px; } #id_content_background { width: 630px; background-color: #FFFFFF; padding: 40px; } #id_text { color: #4B576D; } #id_text a { color: #009EE0; } img.screenshot { text-align: center; margin: 10px 42px 0px 42px; } hr { width: 5em; margin-top: 20px; margin-bottom: 20px; } /* Featured sheet page-specific class styles */ h2.featured_sheet_name { margin-bottom: 0; } .featured_sheet_actions { float:right; text-align: center; font-size: smaller; padding-top: 5px; } #id_text .featured_sheet_actions a { color: #006AA0; } .featured_sheet_actions span { background-color: #D1D2D4; padding: 5px; margin-left: 5px; border-radius: 6px; -moz-border-radius: 6px; } .featured_sheet_description { margin-bottom: 1.5em; } ================================================ FILE: dirigible/shared/static/dirigible/styles/login.css ================================================ #id_login_form_wrap { width: 29em; clear: both; margin-top: 4em; margin-left: auto; margin-right: auto; padding-top: 1.5em; padding-bottom: 1em; font-size: 14pt; } #id_login_form_header { margin-left: 20px; margin-right: 20px; margin-bottom: 30px; } #id_login_signup_blurb { font-size: 12pt; } #id_login_signup_blurb a { text-decoration: underline; } #id_login_form { text-align: center; } #id_username_wrap { clear: both; } #id_username_label { text-align: right; vertical-align: baseline; margin-right: 0.5em; width: 6em; font-weight: bold; } #id_username { text-align: left; vertical-align: baseline; width: 20em; margin-bottom: 1ex; font-size: 14pt; } #id_password_wrap { clear: both; } #id_password_label { text-align: right; vertical-align: baseline; margin-right: 0.5em; width: 6em; font-weight: bold; } #id_password { text-align: left; vertical-align: baseline; width: 20em; margin-bottom: 1ex; font-size: 14pt; } #id_login { clear: both; margin-top: 1em; padding: 0.5em; font-weight: bold; font-size: 14pt; } #id_login_error { margin-top: 0px; } h1 { text-align: left; margin-top: 0px; } ================================================ FILE: dirigible/shared/static/dirigible/styles/non_sheet_page.css ================================================ .centered-block { width: 710px; clear: both; margin-left: auto; margin-right: auto; } #id_ie_warning { padding: 4px; margin-bottom: 10px; } #id_ie_warning a{ text-decoration: underline; } .grey_rounded_corner_box { border: 5px solid #FFFFFF; border-radius: 12px; -moz-border-radius: 12px; background-color: #D1D2D4; color: #4B576D; padding: 15px; } .grey_rounded_corner_box a { color: #4B576D; } .grey_rounded_corner_box a.button { color: #FFFFFF; } #id_container { position: relative; min-height: 100%; } #id_content { padding-bottom: 9em; } #id_big_logo_wrap { margin-top: 8px; margin-bottom: 28px; } #id_big_logo { display: block; margin-left: auto; margin-right: auto; } #id_footer_links { margin-top: 30px; color: #FFFFFF; text-align: center; font-size: 13px; width: 100%; position: absolute; bottom: 7em; } #id_about_us { margin-bottom: 5px; } #id_copyright_wrap { background-color: #4B576D; color: #FFFFFF; padding-top: 20px; height: 5em; width: 100%; text-align: center; font-size: 13px; position: absolute; bottom: 0px; } ================================================ FILE: dirigible/shared/static/dirigible/styles/pricing.css ================================================ #id_beta_free_splash { background-color: #FFEB8C; } #id_price_plan_table { border-spacing: 20px; } #id_price_plan_table td { background-color: #99DDFF; border-radius: 12px; -moz-border-radius: 12px; border: 2px solid #009EE0; padding: 20px; font-size: 12px; } #id_tax { font-size: smaller; } .plan_cell { width: 25%; vertical-align: top; } .plan_cell ul { padding-left: 15px; margin: 0; } .plan_header { margin-bottom: 7px; } .plan_name { font-size: 18px; } .plan_price { margin-top: 4px; font-size: 14px; float: right; } ================================================ FILE: dirigible/shared/static/dirigible/styles/registration.css ================================================ #id_signup_vwrap { margin-top: 3em; } #id_signup_hwrap { width: 30em; clear: both; margin-left: auto; margin-right: auto; padding: 1.5em 2em; } #id_register_intro_text { text-align: center; margin-bottom: 20px; } td.label { font-size: 20px; text-align: right; width: 35%; padding-right: 5px; } td.input { width: 60%; } td.input input{ font-size: 20px; border: 1px solid #888888; width: 95%; padding: 4px; } #id_signup_button { margin-top: 20px; padding-left: 10px; padding-right: 10px; padding-top: 2px; padding-bottom: 2px; height: 31pt; font-size: 20px; font-weight: bold; } #id_signup_table { width: 100%; } td { text-align: right; } ul.errorlist { list-style-type: none; margin: 1em 0 0 0; color: #880000; } #id_password1.error, #id_password2.error { background-color: #FFDDDD; } #id_signup_form { margin-top: 1em; margin-bottom: 1em; } #id_signup_hwrap p#id_link_home_para { margin-top: 1em; !important } #id_link_home { color: #FFFFFF; font-weight: bold; } #id_login_form { text-align: center; } #id_we_dont_spam { text-align: center; margin-top: 20px; } ================================================ FILE: dirigible/shared/static/dirigible/styles/sheet_page.css ================================================ body { /* prevents splitter resizing that sometimes leaves space for scrollbars that are not there. This does mean that there are no scrollbars when the grid, code pane and error console are larger than the window */ overflow: hidden; } #id_header_and_toolbar { /* We explicitly set the header height here so that Chrome doesn't miscalculate the height and cause the bottom of the grid to fall off the window */ } #id_ie_warning { background: #444; margin-top: 13px; margin-right: 13px; padding: 1px 2px; float: right; } #id_copy_sheet_div { padding-top: 45px; margin-left: auto; margin-right: auto; width: 500px; text-align: center; } #id_copy_sheet_div a { text-decoration: underline; } #id_sheet_name_wrap { width: 50%; float: left; } #id_sheet_name { margin-left: 10px; font-size: 20pt; margin-right: 10px; overflow: hidden; height: 2ex; } #edit-id_sheet_name { margin-left: 6px; margin-right: 10px; font-size: 20pt; width: 97%; } .jeip-mouseover { background: #D1D2D4; } /* The icon toolbar */ #id_toolbar_wrap { width: 50%; float: right; } #id_toolbar { padding-left: 20px; padding-right: 20px; text-align: right; } /* Separators above and below the icon toolbar */ .toolbar_line { border: 0 none; padding: 0 0 0 50px; height: 1px; background-color: #5cbee5; color: #5cbee5; } #id_toolbar_buttons { float: right; } #id_spinner_image { float: left; height: 32px; } #id_buffering_message { position: absolute; font-weight: bold; font-size: small; left: 100px; top: 70px; font-family: monospace; } #id_grid_and_code { /* splitter */ min-height: 400px; } #id_left_column a { color: #49546A; } #id_left_column { color: #49546a; margin: 0; width: 60%; /* splitter */ overflow: hidden; } #id_right_column { background: #009EE0; color: #ffffff; margin: 0; padding: 0; border: 0; /* splitter */ overflow: hidden; } /* Usercode editor */ #id_usercode_wrap { position: absolute; top: 0; left: 10px; width: 100%; height: 100%; } #id_usercode { background-color: white; position: absolute; top: -12px; left: 10; } .user_code .heading { background: #2E82AE; } /* Error console */ #id_console_wrap { height: 100px; } #id_console { white-space: pre; overflow: auto; background: white; color: black; margin-top: 10px; margin-left: 10px; } .console_error_text { color: #ff0000; } .console_output_text { color: #000000; } .console_system_text { font-style: italic; color: #000000; } #id_formula_bar { margin: 0 10px 10px 11px; padding: 3px; border: 1px solid #888888; } /* SlickGrid stuff */ #id_grid { padding-left: 10px; padding-right: 10px; } .grid-canvas { background: #ffffff; } .c0 { color: #ffffff; text-align: right; } .even .c0 { background: #2E82AE; border-color: #2E82AE; } .odd .c0 { background: #1a6e9a; border-color: #1a6e9a; } .slick-header-columns div { background: #2E82AE; color: #ffffff; text-align: center; } .slick-header-column.ui-state-default { border-right: 0 !important; } .slick-cell { font-size: 12pt; } .slick-cell.active { margin-left: 0; margin-top: 0; border: solid gray 1px; } .slick-cell.selected { background-color : #C8E9F7; } .slick-cell.active.selected { background-color : #FFFFFF; } input.editor-text { background:none repeat scroll 0 0 transparent; border:0 none; height:100%; margin:0; outline:0 none; padding:0; width:100%; font-size: 12pt; } .grid_formula { color: #BBBBBB; } /* Splitter */ .vsplitbar { width: 6px; background: #80CFF0 url(/static/splitter/img/vgrabber.png) no-repeat center; } .hsplitbar { height: 6px; background: #80CFF0 url(/static/splitter/img/hgrabber.png) no-repeat center; } /* Copyright and "extra links" stuff at the bottom of the page */ .column_footer { position: absolute; bottom: 0; padding-left: 20px; } #id_tutorial_promo_dialog_close { margin-top: 1ex; font-size: large; } #id_import_form form { text-align: right; } #id_import_form_upload_csv_button { font-weight: bold; } #id_import_form_upload_xls_values_button { font-weight: bold; } #id_security_form { } #id_security_form form { } #id_security_form_general_settings { margin-bottom: 2ex; } #id_security_form_api_settings { margin-bottom: 1ex; } #id_security_form_api_subsettings { margin-top: 1ex; margin-left: 2em; } #id_security_form_public_sheet_help { font-size: smaller; margin-left: 2em; } #id_security_form_json_help { font-size: smaller; margin-left: 2em; } #id_security_form_ok_button { font-weight: bold; } #id_security_form_json_api_key { width: 200px; } #id_security_form_json_api_url { width: 200px; } #id_security_form_save_error { font-size: smaller; margin-top: 3ex; } td.label { } ================================================ FILE: dirigible/shared/static/dirigible/styles/user_page.css ================================================ #id_dashboard_wrap { margin-top: 2em; } #id_sheets { width: 413px; float: left; } #id_right_column { float: right; } #id_account_details { width: 187px; border: 5px solid #4B576D; border-radius: 12px; -moz-border-radius: 12px; background-color: #AEAFAF; color: #4B576D; padding: 15px; } #id_example_sheets { width: 187px; border: 5px solid #4B576D; border-radius: 12px; -moz-border-radius: 12px; background-color: #AEAFAF; color: #4B576D; padding: 15px; margin-top: 1em; } #id_create_new_sheet { font-weight: bold; float: right; } .section-title { font-size: 14pt; margin-top: 0px; } #id_sheets_title { display: inline; } #id_sheets_body { clear: both; } #id_no_sheets_message { font-style: italic; margin-left: 1em; } #id_change_password_button { font-weight: bold; margin-left: 2ex; } #id_change_password_form { display: none; } #id_change_password_success { display: none; margin-top: 1em; color: #008000; } #id_change_password_error { margin-top: 1em; color: #800000; } #id_change_password_buttons { margin-top: 1em; float: right; } #id_change_password_form input[type='password'] { margin-bottom: 0.5em; } ================================================ FILE: dirigible/shared/static/dirigible/styles/video.css ================================================ body { background: #000000; } #id_video_div { margin-left: -125px; margin-top: 20px; } ================================================ FILE: dirigible/shared/static/dirigible/tests/cell_editor_test.html ================================================
    Please wait
    ================================================ FILE: dirigible/shared/static/dirigible/tests/console_view_test.html ================================================
    Please wait
    ================================================ FILE: dirigible/shared/static/dirigible/tests/dialogs_test.html ================================================
    Please wait
    ================================================ FILE: dirigible/shared/static/dirigible/tests/editor_commands_test.html ================================================
    Please wait
    ================================================ FILE: dirigible/shared/static/dirigible/tests/feedback_dialog_test.html ================================================
    Please wait
    ================================================ FILE: dirigible/shared/static/dirigible/tests/grid_commands_test.html ================================================
    Please wait
    ================================================ FILE: dirigible/shared/static/dirigible/tests/grid_content_converter_test.html ================================================
    Please wait
    ================================================ FILE: dirigible/shared/static/dirigible/tests/grid_interaction_handler_test.html ================================================
    Please wait
    ================================================ FILE: dirigible/shared/static/dirigible/tests/grid_remote_model_test.html ================================================
    Please wait
    ================================================ FILE: dirigible/shared/static/dirigible/tests/grid_view_test.html ================================================
    Please wait
    ================================================ FILE: dirigible/shared/static/dirigible/tests/htmlescape_test.html ================================================
    Please wait
    ================================================ FILE: dirigible/shared/static/dirigible/tests/jsmock.js ================================================ /* * JSMock 1.2.2, a mock object library for JavaScript * Copyright (C) 2006 Justin DeWind * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ JSMock = { extend: function(object) { var mockControl = new MockControl(); object.createMock = function(objectToMock) {return mockControl.createMock(objectToMock)}; object.resetMocks = function() {mockControl.reset()}; object.verifyMocks = function() {mockControl.verify()}; if(!object.tearDown) { object.tearDown = function() { object.verifyMocks(); } } else if(object.tearDown.constructor == Function) { object.__oldTearDown__ = object.tearDown; object.tearDown = function() { object.__oldTearDown__(); object.verifyMocks(); } } } } function MockControl() { this.__expectationMatcher = new ExpectationMatcher(); this.__lastMock = null; this.__lastCallName = null; } MockControl.prototype = { createMock: function(objectToMock) { var mock = { calls: [], expects: function() {this.__recording = true; return this}, __recording: false}; mock.expect = mock.expects; if(objectToMock != null) { if( typeof(objectToMock) == 'function' ) { this.__createMethods(objectToMock, mock); this.__createMethods(new objectToMock(), mock); } else if( typeof(objectToMock) == 'object') { this.__createMethods(objectToMock, mock); } else { throw new Error("Cannot mock out a " + typeof(objectToMock)); } } var self = this; mock.addMockMethod = function(method) { self.__createMethod(self, mock, method); } return mock; }, andReturn: function(returnValue) { this.__verifyLastMockNotNull("Cannot set return value without an expectation"); this.__initializeReturnExpectationForMock(); this.__lastMock.calls[this.__lastCallName].push( function() { return returnValue; }); }, andThrow: function(throwMsg) { this.__verifyLastMockNotNull("Cannot throw error without an expectation"); this.__initializeReturnExpectationForMock(); this.__lastMock.calls[this.__lastCallName].push( function() { throw new Error(throwMsg); }); }, andStub: function(block) { this.__verifyLastMockNotNull("Cannot stub without an expectation"); if( typeof(block) != 'function') { throw new Error("Stub must be a function"); } this.__initializeReturnExpectationForMock(); this.__lastMock.calls[this.__lastCallName].push( function() { return block.apply(this, arguments); }); }, reset: function() { this.__expectationMatcher.reset(); }, verify: function() { if(!this.__expectationMatcher.matches()) { discrepancy = this.__expectationMatcher.discrepancy(); message = discrepancy.message; method = discrepancy.behavior.method formattedArgs = ArgumentFormatter.format(discrepancy.behavior.methodArguments); this.__expectationMatcher.reset(); throw new Error(message + ": " + method + "(" + formattedArgs + ")"); } else { this.__expectationMatcher.reset(); } }, __createMethods: function(object, mock) { for( property in object ) { if( this.__isPublicMethod(object, property) ) { this.__createMethod( this, mock, property ); } } }, __createMethod: function(control, mock, method) { mock[method] = function() { if( mock.__recording ) { control.__lastMock = mock; control.__lastCallName = method; control.__expectationMatcher.addExpectedMethodCall( mock, method, arguments ); mock.__recording = false; return control; } else { control.__expectationMatcher.addActualMethodCall( mock, method, arguments ); if( mock.calls[method] != null) { returnValue = mock.calls[method].shift(); if( typeof(returnValue) == 'function') { return returnValue.apply(this, arguments); } } } } }, __isPublicMethod: function(object, property) { try { return typeof(object[property]) == 'function' && property.charAt(0) != "_"; } catch(e) { return false; } }, __verifyLastMockNotNull: function(throwMsg) { if(this.__lastMock == null) { throw new Error(throwMsg); } }, __initializeReturnExpectationForMock: function() { if(typeof(this.__lastMock.calls[this.__lastCallName]) == 'undefined') { this.__lastMock.calls[this.__lastCallName] = []; } } } function ExpectationMatcher() { this.__expectationBehaviorList = []; this.__actualBehaviorList = []; this.__discrepancy = null; } ExpectationMatcher.prototype = { addExpectedMethodCall: function(caller, method, methodArguments ) { this.__expectationBehaviorList.push(new InvocationBehavior(caller, method, methodArguments)); }, addActualMethodCall: function(caller, method, methodArguments ) { this.__actualBehaviorList.push(new InvocationBehavior(caller, method, methodArguments)); }, matches: function() { var self = this; var matches = true; this.__expectationBehaviorList.eachIndexForJsMock(function(index, expectedBehavior) { var actualBehavior = (self.__actualBehaviorList.length > index) ? self.__actualBehaviorList[index] : null; if(matches) { if( actualBehavior === null ) { self.__discrepancy = new Discrepancy("Expected function not called", expectedBehavior); matches = false; } else if( expectedBehavior.method != actualBehavior.method ) { self.__discrepancy = new Discrepancy("Surprise call", actualBehavior); matches = false; } else if( expectedBehavior.caller != actualBehavior.caller ) { self.__discrepancy = new Discrepancy("Surprise call from unexpected caller", actualBehavior); matches = false; } else if( !self.__matchArguments(expectedBehavior.methodArguments, actualBehavior.methodArguments) ) { self.__discrepancy = new Discrepancy("Unexpected Arguments", actualBehavior); matches = false; } } }); if( this.__actualBehaviorList.length > this.__expectationBehaviorList.length && matches ) { this.__discrepancy = new Discrepancy("Surprise call", this.__actualBehaviorList[this.__expectationBehaviorList.length]); matches = false } return matches; }, reset: function() { this.__expectationBehaviorList = []; this.__actualBehaviorList = []; this.__discrepancy = null; }, discrepancy: function() { return this.__discrepancy; }, __matchArguments: function(expectedArgs, actualArgs) { var expectedArray = this.__convertArgumentsToArray(expectedArgs); var actualArray = this.__convertArgumentsToArray(actualArgs); return ArgumentMatcher.matches(expectedArray, actualArray); }, __convertArgumentsToArray: function(args) { var convertedArguments = []; for(var i = 0; i < args.length; i++) { convertedArguments[i] = args[i]; } return convertedArguments; } } function InvocationBehavior(caller, method, methodArguments) { this.caller = caller; this.method = method; this.methodArguments = methodArguments; } function TypeOf(type) { if(typeof(type) != 'function') throw new Error("Can only take constructors"); this.type = type; } TypeOf.isA = function(type) { return new TypeOf(type); }; ArgumentMatcher = { matches: function(expected, actual) { return this.__delegateMatching(expected, actual); }, __delegateMatching: function(expected, actual) { if( expected == null ) { return this.__match( expected, actual ); } else if( expected.constructor == TypeOf ) { return this.__match(expected.type, actual.constructor); } else if( expected.constructor == Array ) { return this.__matchArrays(expected, actual); } else { return this.__match(expected, actual); } }, __match: function(expected, actual) { return ( expected == actual ); }, __matchArrays: function(expected, actual) { if ( actual == null) return false; if( actual.constructor != Array) return false; if( expected.length != actual.length ) return false; for(var i = 0; i < expected.length; i++ ) { if( !this.__delegateMatching(expected[i], actual[i]) ) return false; } return true; } } function Discrepancy(message, behavior) { if(behavior.constructor != InvocationBehavior) throw new Error("The behavior can only be an InvocationBehavior object"); this.message = message; this.behavior = behavior; } ArgumentFormatter = { format: function(args) { var formattedArgs = ""; for(var i = 0; i < args.length; i++) { if( args[i] == null ) { formattedArgs += ( formattedArgs == "" ) ? "null" : ", " + "null"; } else if( args[i].constructor == TypeOf || args[i].constructor == Function) { var func = ( args[i].constructor == TypeOf ) ? args[i].type : args[i]; formattedArgs += ( formattedArgs == "" ) ? this.__formatFunction(func) : ", " + this.__formatFunction(func); } else if( typeof(args[i]) == "string" ) { formattedArgs += ( formattedArgs == "" ) ? "\"" + args[i].toString() + "\"" : ", \"" + args[i].toString() + "\"" } else if( args[i].constructor == Array ) { formattedArgs += ( formattedArgs == "" ) ? "[" + this.format(args[i]) + "]" : ", [" + this.format(args[i]) + "]"; } else { formattedArgs += ( formattedArgs == "" ) ? args[i].toString() : ", " + args[i].toString(); } } return formattedArgs; }, __formatFunction: function(func) { // Manual checking is done for internal/native functions // since Safari will not display them correctly // for the intended regex parsing. if(func == Array) { return "Array"; } else if(func == Date) { return "Date"; } else if(func == Object) { return "Object"; } else if(func == String) { return "String"; } else if(func == Function) { return "Function"; } else if(func == RegExp) { return "RegExp"; } else if(func == Error) { return "Error"; } else if(func == Number) { return "Number"; } else if(func == Boolean) { return "Boolean"; } var formattedFunc = func.toString().match(/function (\w+)/); return ( formattedFunc == null ) ? "{{Closure}}" : formattedFunc[1]; } } /* Helpers */ // Implemented each method with a unique name to avoid conflicting // with other libraries that implement it. Array.prototype.eachIndexForJsMock = function(block) { for(var index = 0; index < this.length; index++) { block(index, this[index]); } } ================================================ FILE: dirigible/shared/static/dirigible/tests/logger.css ================================================ /* Copyright (c) 2008, Yahoo! Inc. All rights reserved. Code licensed under the BSD License: http://developer.yahoo.net/yui/license.txt version: 2.6.0 */ /* logger default styles */ /* default width: 31em */ /* default font-size 77% */ .yui-log {padding:1em;width:31em;background-color:#AAA;color:#000;border:1px solid black;font-family:monospace;font-size:77%;text-align:left;z-index:9000;} /* for containers built from scratch */ .yui-log-container {position:absolute;top:1em;right:1em;} /* buttons */ .yui-log input { margin:0;padding:0; font-family:arial; font-size:100%; font-weight:normal; } .yui-log .yui-log-btns {position:relative;float:right;bottom:.25em;} /* header */ .yui-log .yui-log-hd {margin-top:1em;padding:.5em;background-color:#575757;} .yui-log .yui-log-hd h4 {margin:0;padding:0;font-size:107%;font-weight:bold;color:#FFF;} /* body */ .yui-log .yui-log-bd {width:100%;height:20em;background-color:#FFF;border:1px solid gray;overflow:auto;} /* height is controlled here: default 20em*/ .yui-log p {margin:1px;padding:.1em;} .yui-log pre {margin:0;padding:0;} /* for pre to respect newlines yet wrap long lines */ /* http://www.longren.org/2006/09/27/wrapping-text-inside-pre-tags/ */ .yui-log pre.yui-log-verbose { white-space: pre-wrap; /* css-3 */ white-space: -moz-pre-wrap !important; /* Mozilla, since 1999 */ white-space: -pre-wrap; /* Opera 4-6 */ white-space: -o-pre-wrap; /* Opera 7 */ word-wrap: break-word; /* Internet Explorer 5.5+ */ } /* footer */ .yui-log .yui-log-ft {margin-top:.5em;} .yui-log .yui-log-ft .yui-log-categoryfilters {} .yui-log .yui-log-ft .yui-log-sourcefilters {width:100%;border-top:1px solid #575757;margin-top:.75em;padding-top:.75em;} .yui-log .yui-log-filtergrp {margin-right:.5em;} /*.yui-log .yui-log-ft ul {margin:0;padding:0;line-height:1.8} .yui-log .yui-log-ft li {list-style:none;display:inline;white-space:nowrap;}*/ /* logs */ .yui-log .info {background-color:#A7CC25;} /* A7CC25 green */ .yui-log .warn {background-color:#F58516;} /* F58516 orange */ .yui-log .error {background-color:#E32F0B;} /* E32F0B red */ .yui-log .time {background-color:#A6C9D7;} /* A6C9D7 blue */ .yui-log .window {background-color:#F2E886;} /* F2E886 tan */ ================================================ FILE: dirigible/shared/static/dirigible/tests/page_commands_test.html ================================================
    Please wait
    ================================================ FILE: dirigible/shared/static/dirigible/tests/page_interaction_handler_test.html ================================================
    Please wait
    test sheet name
    ================================================ FILE: dirigible/shared/static/dirigible/tests/page_view_test.html ================================================
    Please wait
    ================================================ FILE: dirigible/shared/static/dirigible/tests/security_settings_test.html ================================================
    Please wait
    ================================================ FILE: dirigible/shared/static/dirigible/tests/selection_model_test.html ================================================
    Please wait
    ================================================ FILE: dirigible/shared/static/dirigible/tests/sheet_page_utils_test.html ================================================
    Please wait
    ================================================ FILE: dirigible/shared/static/dirigible/tests/test_utils.js ================================================ // Copyright (c) 2010 Resolver Systems Ltd, PythonAnywhere LLP // See LICENSE.md // function elementVisible(element, expected, message) { if (message == undefined) { message = ''; } else { message = ': ' + message } YAHOO.util.Assert.isTrue(element.length > 0, 'element(s) should exist' + message); actual = element.is(':visible'); var ids = ''; element.each(function () {ids += this.id;}); YAHOO.util.Assert.areSame(expected, actual, 'wrong visibility for ' + ids + message); } function assertDeepAreSame(expected, actual, message) { if (message == undefined) { message = ""; } else { message = message + " Path: "; } var finalMessage = _deepAreSame(expected, actual, message); if (finalMessage){ throw new YAHOO.util.ComparisonFailure(finalMessage, expected, actual); } } function _deepAreSame(expected, actual, message) { if (typeof(expected) !== typeof(actual)) { if (actual === undefined) { return message + " property missing from actual"; } return ( message + ' types differ ' + '(expected is ' + typeof(expected) + ', actual is ' + typeof(actual) + ')' ); } switch (typeof(expected)) { case 'object': for (p in expected) { var recurseDown = _deepAreSame( expected[p], actual[p], message + '.' + p ); if (recurseDown) { return recurseDown; } } break; case 'function': if (typeof(actual) == 'undefined' || (expected.toString() != actual.toString()) ) { return ( message + " methods exist on both " + "expected and actual but differ\n" + "expected : " + expected.toString() + "\n" + "actual : " + actual.toString() ); } break; default: if (expected !== actual){ return ( message + ' property differs, ' + expected + ' !== ' + actual ); } break; } for (p in actual) { if ( typeof(actual[p]) != 'undefined' && typeof(expected[p]) == 'undefined' ) { return message + "." + p + " property unexpectedly on actual"; } } } ================================================ FILE: dirigible/shared/static/dirigible/tests/test_utils_test.html ================================================
    Please wait
    ================================================ FILE: dirigible/shared/static/dirigible/tests/testlogger.css ================================================ /* Copyright (c) 2008, Yahoo! Inc. All rights reserved. Code licensed under the BSD License: http://developer.yahoo.net/yui/license.txt version: 2.6.0 */ .yui-log {padding-top:3em;} /* .yui-log-container {position:relative;width:60em} .yui-log .yui-log-bd {height:auto; overflow:visible;} */ .yui-log-container {width:70em} .yui-log .yui-log-bd {height:60em} .yui-log .yui-log-btns {display:none;} .yui-log .yui-log-ft .yui-log-sourcefilters {visibility:hidden;} .yui-log .yui-log-hd {display:none;} .yui-log .yui-log-ft {position:absolute;top:0em;} .pass { background-color: green; font-weight: bold; color: white; } .fail { background-color: red; font-weight: bold; color: white; } .ignore { background-color: #666; font-weight: bold; color: white; } ================================================ FILE: dirigible/shared/static/dirigible/tests/toolbar_interaction_handler_test.html ================================================
    Please wait
    Cut Copy Paste Recalculate ================================================ FILE: dirigible/shared/static/dirigible/tests/usercode_view_test.html ================================================
    Please wait
    ================================================ FILE: dirigible/shared/static/dirigible/tests/yuirunner.js ================================================ function escape_quotes(key, value) { if (typeof value === 'string') { // just replacing '<' seems enough to cause json encoding to // kick in for '>', etc. return value.replace(/\0)?B.dump(I[K],N-1):Q);}else{P.push(I[K]);}P.push(O);}if(P.length>1){P.pop();}P.push("]");}else{P.push("{");for(K in I){if(B.hasOwnProperty(I,K)){P.push(K+L);if(B.isObject(I[K])){P.push((N>0)?B.dump(I[K],N-1):Q);}else{P.push(I[K]);}P.push(O);}}if(P.length>1){P.pop();}P.push("}");}return P.join("");},substitute:function(Y,J,R){var N,M,L,U,V,X,T=[],K,O="dump",S=" ",I="{",W="}",Q,P;for(;;){N=Y.lastIndexOf(I);if(N<0){break;}M=Y.indexOf(W,N);if(N+1>=M){break;}K=Y.substring(N+1,M);U=K;X=null;L=U.indexOf(S);if(L>-1){X=U.substring(L+1);U=U.substring(0,L);}V=J[U];if(R){V=R(U,V,X);}if(B.isObject(V)){if(B.isArray(V)){V=B.dump(V,parseInt(X,10));}else{X=X||"";Q=X.indexOf(O);if(Q>-1){X=X.substring(4);}P=V.toString();if(P===G||Q>-1){V=B.dump(V,parseInt(X,10));}else{V=P;}}}else{if(!B.isString(V)&&!B.isNumber(V)){V="~-"+T.length+"-~";T[T.length]=K;}}Y=Y.substring(0,N)+V+Y.substring(M+1);}for(N=T.length-1;N>=0;N=N-1){Y=Y.replace(new RegExp("~-"+N+"-~"),"{"+T[N]+"}","g");}return Y;},trim:function(I){try{return I.replace(/^\s+|\s+$/g,"");}catch(J){return I;}},merge:function(){var L={},J=arguments,I=J.length,K;for(K=0;K519)?true:false);while((G=G[u])){z[0]+=G[b];z[1]+=G[P];if(AC){z=E.Dom._calcBorders(G,z);}}if(E.Dom._getStyle(y,p)!==f){G=y;while((G=G[Z])&&G[C]){AA=G[i];AB=G[O];if(H&&(E.Dom._getStyle(G,"overflow")!=="visible")){z=E.Dom._calcBorders(G,z);}if(AA||AB){z[0]-=AB;z[1]-=AA;}}z[0]+=x;z[1]+=Y;}else{if(D){z[0]-=x;z[1]-=Y;}else{if(I||H){z[0]+=x;z[1]+=Y;}}}z[0]=Math.floor(z[0]);z[1]=Math.floor(z[1]);}else{}return z;};}}(),getX:function(G){var Y=function(x){return E.Dom.getXY(x)[0];};return E.Dom.batch(G,Y,E.Dom,true);},getY:function(G){var Y=function(x){return E.Dom.getXY(x)[1];};return E.Dom.batch(G,Y,E.Dom,true);},setXY:function(G,x,Y){E.Dom.batch(G,E.Dom._setXY,{pos:x,noRetry:Y});},_setXY:function(G,z){var AA=E.Dom._getStyle(G,p),y=E.Dom.setStyle,AD=z.pos,Y=z.noRetry,AB=[parseInt(E.Dom.getComputedStyle(G,j),10),parseInt(E.Dom.getComputedStyle(G,o),10)],AC,x;if(AA=="static"){AA=V;y(G,p,AA);}AC=E.Dom._getXY(G);if(!AD||AC===false){return false;}if(isNaN(AB[0])){AB[0]=(AA==V)?0:G[b];}if(isNaN(AB[1])){AB[1]=(AA==V)?0:G[P];}if(AD[0]!==null){y(G,j,AD[0]-AC[0]+AB[0]+"px");}if(AD[1]!==null){y(G,o,AD[1]-AC[1]+AB[1]+"px");}if(!Y){x=E.Dom._getXY(G);if((AD[0]!==null&&x[0]!=AD[0])||(AD[1]!==null&&x[1]!=AD[1])){E.Dom._setXY(G,{pos:AD,noRetry:true});}}},setX:function(Y,G){E.Dom.setXY(Y,[G,null]);},setY:function(G,Y){E.Dom.setXY(G,[null,Y]);},getRegion:function(G){var Y=function(x){var y=false;if(E.Dom._canPosition(x)){y=E.Region.getRegion(x);}else{}return y;};return E.Dom.batch(G,Y,E.Dom,true);},getClientWidth:function(){return E.Dom.getViewportWidth();},getClientHeight:function(){return E.Dom.getViewportHeight();},getElementsByClassName:function(AB,AF,AC,AE,x,AD){AF=AF||"*";AC=(AC)?E.Dom.get(AC):null||K;if(!AC){return[];}var Y=[],G=AC.getElementsByTagName(AF),z=E.Dom.hasClass;for(var y=0,AA=G.length;y-1;}}else{}return G;},addClass:function(Y,G){return E.Dom.batch(Y,E.Dom._addClass,G);},_addClass:function(x,Y){var G=false,y;if(x&&Y){y=E.Dom._getAttribute(x,F)||J;if(!E.Dom._hasClass(x,Y)){E.Dom.setAttribute(x,F,A(y+B+Y));G=true;}}else{}return G;},removeClass:function(Y,G){return E.Dom.batch(Y,E.Dom._removeClass,G);},_removeClass:function(y,x){var Y=false,AA,z,G;if(y&&x){AA=E.Dom._getAttribute(y,F)||J;E.Dom.setAttribute(y,F,AA.replace(E.Dom._getClassRegex(x),J));z=E.Dom._getAttribute(y,F);if(AA!==z){E.Dom.setAttribute(y,F,A(z));Y=true;if(E.Dom._getAttribute(y,F)===""){G=(y.hasAttribute&&y.hasAttribute(g))?g:F; y.removeAttribute(G);}}}else{}return Y;},replaceClass:function(x,Y,G){return E.Dom.batch(x,E.Dom._replaceClass,{from:Y,to:G});},_replaceClass:function(y,x){var Y,AB,AA,G=false,z;if(y&&x){AB=x.from;AA=x.to;if(!AA){G=false;}else{if(!AB){G=E.Dom._addClass(y,x.to);}else{if(AB!==AA){z=E.Dom._getAttribute(y,F)||J;Y=(B+z.replace(E.Dom._getClassRegex(AB),B+AA)).split(E.Dom._getClassRegex(AA));Y.splice(1,0,B+AA);E.Dom.setAttribute(y,F,A(Y.join(J)));G=true;}}}}else{}return G;},generateId:function(G,x){x=x||"yui-gen";var Y=function(y){if(y&&y.id){return y.id;}var z=x+YAHOO.env._id_counter++;if(y){if(y[e]&&y[e].getElementById(z)){return E.Dom.generateId(y,z+x);}y.id=z;}return z;};return E.Dom.batch(G,Y,E.Dom,true)||Y.apply(E.Dom,arguments);},isAncestor:function(Y,x){Y=E.Dom.get(Y);x=E.Dom.get(x);var G=false;if((Y&&x)&&(Y[l]&&x[l])){if(Y.contains&&Y!==x){G=Y.contains(x);}else{if(Y.compareDocumentPosition){G=!!(Y.compareDocumentPosition(x)&16);}}}else{}return G;},inDocument:function(G,Y){return E.Dom._inDoc(E.Dom.get(G),Y);},_inDoc:function(Y,x){var G=false;if(Y&&Y[C]){x=x||Y[e];G=E.Dom.isAncestor(x[v],Y);}else{}return G;},getElementsBy:function(Y,AF,AB,AD,y,AC,AE){AF=AF||"*";AB=(AB)?E.Dom.get(AB):null||K;if(!AB){return[];}var x=[],G=AB.getElementsByTagName(AF);for(var z=0,AA=G.length;z=8&&K.documentElement.hasAttribute){E.Dom.DOT_ATTRIBUTES.type=true;}})();YAHOO.util.Region=function(C,D,A,B){this.top=C;this.y=C;this[1]=C;this.right=D;this.bottom=A;this.left=B;this.x=B;this[0]=B; this.width=this.right-this.left;this.height=this.bottom-this.top;};YAHOO.util.Region.prototype.contains=function(A){return(A.left>=this.left&&A.right<=this.right&&A.top>=this.top&&A.bottom<=this.bottom);};YAHOO.util.Region.prototype.getArea=function(){return((this.bottom-this.top)*(this.right-this.left));};YAHOO.util.Region.prototype.intersect=function(E){var C=Math.max(this.top,E.top),D=Math.min(this.right,E.right),A=Math.min(this.bottom,E.bottom),B=Math.max(this.left,E.left);if(A>=C&&D>=B){return new YAHOO.util.Region(C,D,A,B);}else{return null;}};YAHOO.util.Region.prototype.union=function(E){var C=Math.min(this.top,E.top),D=Math.max(this.right,E.right),A=Math.max(this.bottom,E.bottom),B=Math.min(this.left,E.left);return new YAHOO.util.Region(C,D,A,B);};YAHOO.util.Region.prototype.toString=function(){return("Region {"+"top: "+this.top+", right: "+this.right+", bottom: "+this.bottom+", left: "+this.left+", height: "+this.height+", width: "+this.width+"}");};YAHOO.util.Region.getRegion=function(D){var F=YAHOO.util.Dom.getXY(D),C=F[1],E=F[0]+D.offsetWidth,A=F[1]+D.offsetHeight,B=F[0];return new YAHOO.util.Region(C,E,A,B);};YAHOO.util.Point=function(A,B){if(YAHOO.lang.isArray(A)){B=A[1];A=A[0];}YAHOO.util.Point.superclass.constructor.call(this,B,A,B,A);};YAHOO.extend(YAHOO.util.Point,YAHOO.util.Region);(function(){var B=YAHOO.util,A="clientTop",F="clientLeft",J="parentNode",K="right",W="hasLayout",I="px",U="opacity",L="auto",D="borderLeftWidth",G="borderTopWidth",P="borderRightWidth",V="borderBottomWidth",S="visible",Q="transparent",N="height",E="width",H="style",T="currentStyle",R=/^width|height$/,O=/^(\d[.\d]*)+(em|ex|px|gd|rem|vw|vh|vm|ch|mm|cm|in|pt|pc|deg|rad|ms|s|hz|khz|%){1}?/i,M={get:function(X,Z){var Y="",a=X[T][Z];if(Z===U){Y=B.Dom.getStyle(X,U);}else{if(!a||(a.indexOf&&a.indexOf(I)>-1)){Y=a;}else{if(B.Dom.IE_COMPUTED[Z]){Y=B.Dom.IE_COMPUTED[Z](X,Z);}else{if(O.test(a)){Y=B.Dom.IE.ComputedStyle.getPixel(X,Z);}else{Y=a;}}}}return Y;},getOffset:function(Z,e){var b=Z[T][e],X=e.charAt(0).toUpperCase()+e.substr(1),c="offset"+X,Y="pixel"+X,a="",d;if(b==L){d=Z[c];if(d===undefined){a=0;}a=d;if(R.test(e)){Z[H][e]=d;if(Z[c]>d){a=d-(Z[c]-d);}Z[H][e]=L;}}else{if(!Z[H][Y]&&!Z[H][e]){Z[H][e]=b;}a=Z[H][Y];}return a+I;},getBorderWidth:function(X,Z){var Y=null;if(!X[T][W]){X[H].zoom=1;}switch(Z){case G:Y=X[A];break;case V:Y=X.offsetHeight-X.clientHeight-X[A];break;case D:Y=X[F];break;case P:Y=X.offsetWidth-X.clientWidth-X[F];break;}return Y+I;},getPixel:function(Y,X){var a=null,b=Y[T][K],Z=Y[T][X];Y[H][K]=Z;a=Y[H].pixelRight;Y[H][K]=b;return a+I;},getMargin:function(Y,X){var Z;if(Y[T][X]==L){Z=0+I;}else{Z=B.Dom.IE.ComputedStyle.getPixel(Y,X);}return Z;},getVisibility:function(Y,X){var Z;while((Z=Y[T])&&Z[X]=="inherit"){Y=Y[J];}return(Z)?Z[X]:S;},getColor:function(Y,X){return B.Dom.Color.toRGB(Y[T][X])||Q;},getBorderColor:function(Y,X){var Z=Y[T],a=Z[X]||Z.color;return B.Dom.Color.toRGB(B.Dom.Color.toHex(a));}},C={};C.top=C.right=C.bottom=C.left=C[E]=C[N]=M.getOffset;C.color=M.getColor;C[G]=C[P]=C[V]=C[D]=M.getBorderWidth;C.marginTop=C.marginRight=C.marginBottom=C.marginLeft=M.getMargin;C.visibility=M.getVisibility;C.borderColor=C.borderTopColor=C.borderRightColor=C.borderBottomColor=C.borderLeftColor=M.getBorderColor;B.Dom.IE_COMPUTED=C;B.Dom.IE_ComputedStyle=M;})();(function(){var C="toString",A=parseInt,B=RegExp,D=YAHOO.util;D.Dom.Color={KEYWORDS:{black:"000",silver:"c0c0c0",gray:"808080",white:"fff",maroon:"800000",red:"f00",purple:"800080",fuchsia:"f0f",green:"008000",lime:"0f0",olive:"808000",yellow:"ff0",navy:"000080",blue:"00f",teal:"008080",aqua:"0ff"},re_RGB:/^rgb\(([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\)$/i,re_hex:/^#?([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})$/i,re_hex3:/([0-9A-F])/gi,toRGB:function(E){if(!D.Dom.Color.re_RGB.test(E)){E=D.Dom.Color.toHex(E);}if(D.Dom.Color.re_hex.exec(E)){E="rgb("+[A(B.$1,16),A(B.$2,16),A(B.$3,16)].join(", ")+")";}return E;},toHex:function(H){H=D.Dom.Color.KEYWORDS[H]||H;if(D.Dom.Color.re_RGB.exec(H)){var G=(B.$1.length===1)?"0"+B.$1:Number(B.$1),F=(B.$2.length===1)?"0"+B.$2:Number(B.$2),E=(B.$3.length===1)?"0"+B.$3:Number(B.$3);H=[G[C](16),F[C](16),E[C](16)].join("");}if(H.length<6){H=H.replace(D.Dom.Color.re_hex3,"$1$1");}if(H!=="transparent"&&H.indexOf("#")<0){H="#"+H;}return H.toLowerCase();}};}());YAHOO.register("dom",YAHOO.util.Dom,{version:"2.8.1",build:"19"});YAHOO.util.CustomEvent=function(D,C,B,A,E){this.type=D;this.scope=C||window;this.silent=B;this.fireOnce=E;this.fired=false;this.firedWith=null;this.signature=A||YAHOO.util.CustomEvent.LIST;this.subscribers=[];if(!this.silent){}var F="_YUICEOnSubscribe";if(D!==F){this.subscribeEvent=new YAHOO.util.CustomEvent(F,this,true);}this.lastError=null;};YAHOO.util.CustomEvent.LIST=0;YAHOO.util.CustomEvent.FLAT=1;YAHOO.util.CustomEvent.prototype={subscribe:function(B,C,D){if(!B){throw new Error("Invalid callback for subscriber to '"+this.type+"'");}if(this.subscribeEvent){this.subscribeEvent.fire(B,C,D);}var A=new YAHOO.util.Subscriber(B,C,D);if(this.fireOnce&&this.fired){this.notify(A,this.firedWith);}else{this.subscribers.push(A);}},unsubscribe:function(D,F){if(!D){return this.unsubscribeAll();}var E=false;for(var B=0,A=this.subscribers.length;B0){H=C[0];}try{B=F.fn.call(E,H,F.obj);}catch(G){this.lastError=G;if(A){throw G;}}}else{try{B=F.fn.call(E,this.type,C,F.obj);}catch(D){this.lastError=D;if(A){throw D;}}}return B;},unsubscribeAll:function(){var A=this.subscribers.length,B;for(B=A-1;B>-1;B--){this._delete(B);}this.subscribers=[];return A;},_delete:function(A){var B=this.subscribers[A];if(B){delete B.fn;delete B.obj;}this.subscribers.splice(A,1);},toString:function(){return"CustomEvent: "+"'"+this.type+"', "+"context: "+this.scope;}};YAHOO.util.Subscriber=function(A,B,C){this.fn=A;this.obj=YAHOO.lang.isUndefined(B)?null:B;this.overrideContext=C;};YAHOO.util.Subscriber.prototype.getScope=function(A){if(this.overrideContext){if(this.overrideContext===true){return this.obj;}else{return this.overrideContext;}}return A;};YAHOO.util.Subscriber.prototype.contains=function(A,B){if(B){return(this.fn==A&&this.obj==B);}else{return(this.fn==A);}};YAHOO.util.Subscriber.prototype.toString=function(){return"Subscriber { obj: "+this.obj+", overrideContext: "+(this.overrideContext||"no")+" }";};if(!YAHOO.util.Event){YAHOO.util.Event=function(){var G=false,H=[],J=[],A=0,E=[],B=0,C={63232:38,63233:40,63234:37,63235:39,63276:33,63277:34,25:9},D=YAHOO.env.ua.ie,F="focusin",I="focusout";return{POLL_RETRYS:500,POLL_INTERVAL:40,EL:0,TYPE:1,FN:2,WFN:3,UNLOAD_OBJ:3,ADJ_SCOPE:4,OBJ:5,OVERRIDE:6,CAPTURE:7,lastError:null,isSafari:YAHOO.env.ua.webkit,webkit:YAHOO.env.ua.webkit,isIE:D,_interval:null,_dri:null,_specialTypes:{focusin:(D?"focusin":"focus"),focusout:(D?"focusout":"blur")},DOMReady:false,throwErrors:false,startInterval:function(){if(!this._interval){this._interval=YAHOO.lang.later(this.POLL_INTERVAL,this,this._tryPreloadAttach,null,true);}},onAvailable:function(Q,M,O,P,N){var K=(YAHOO.lang.isString(Q))?[Q]:Q;for(var L=0;L-1;M--){S=(this.removeListener(L[M],K,R)&&S);}return S;}}if(!R||!R.call){return this.purgeElement(L,false,K);}if("unload"==K){for(M=J.length-1;M>-1;M--){U=J[M];if(U&&U[0]==L&&U[1]==K&&U[2]==R){J.splice(M,1);return true;}}return false;}var N=null;var O=arguments[3];if("undefined"===typeof O){O=this._getCacheIndex(H,L,K,R);}if(O>=0){N=H[O];}if(!L||!N){return false;}var T=N[this.CAPTURE]===true?true:false;try{this._simpleRemove(L,K,N[this.WFN],T);}catch(Q){this.lastError=Q;return false;}delete H[O][this.WFN];delete H[O][this.FN];H.splice(O,1);return true;},getTarget:function(M,L){var K=M.target||M.srcElement;return this.resolveTextNode(K);},resolveTextNode:function(L){try{if(L&&3==L.nodeType){return L.parentNode;}}catch(K){}return L;},getPageX:function(L){var K=L.pageX;if(!K&&0!==K){K=L.clientX||0;if(this.isIE){K+=this._getScrollLeft();}}return K;},getPageY:function(K){var L=K.pageY;if(!L&&0!==L){L=K.clientY||0;if(this.isIE){L+=this._getScrollTop();}}return L;},getXY:function(K){return[this.getPageX(K),this.getPageY(K)];},getRelatedTarget:function(L){var K=L.relatedTarget;if(!K){if(L.type=="mouseout"){K=L.toElement; }else{if(L.type=="mouseover"){K=L.fromElement;}}}return this.resolveTextNode(K);},getTime:function(M){if(!M.time){var L=new Date().getTime();try{M.time=L;}catch(K){this.lastError=K;return L;}}return M.time;},stopEvent:function(K){this.stopPropagation(K);this.preventDefault(K);},stopPropagation:function(K){if(K.stopPropagation){K.stopPropagation();}else{K.cancelBubble=true;}},preventDefault:function(K){if(K.preventDefault){K.preventDefault();}else{K.returnValue=false;}},getEvent:function(M,K){var L=M||window.event;if(!L){var N=this.getEvent.caller;while(N){L=N.arguments[0];if(L&&Event==L.constructor){break;}N=N.caller;}}return L;},getCharCode:function(L){var K=L.keyCode||L.charCode||0;if(YAHOO.env.ua.webkit&&(K in C)){K=C[K];}return K;},_getCacheIndex:function(M,P,Q,O){for(var N=0,L=M.length;N0&&E.length>0);}var P=[];var R=function(T,U){var S=T;if(U.overrideContext){if(U.overrideContext===true){S=U.obj;}else{S=U.overrideContext;}}U.fn.call(S,U.obj);};var L,K,O,N,M=[];for(L=0,K=E.length;L-1;L--){O=E[L];if(!O||!O.id){E.splice(L,1);}}this.startInterval();}else{if(this._interval){this._interval.cancel();this._interval=null;}}this.locked=false;},purgeElement:function(O,P,R){var M=(YAHOO.lang.isString(O))?this.getEl(O):O;var Q=this.getListeners(M,R),N,K;if(Q){for(N=Q.length-1;N>-1;N--){var L=Q[N];this.removeListener(M,L.type,L.fn);}}if(P&&M&&M.childNodes){for(N=0,K=M.childNodes.length;N-1;N--){M=H[N];if(M){L.removeListener(M[L.EL],M[L.TYPE],M[L.FN],N);}}M=null;}L._simpleRemove(window,"unload",L._unload);},_getScrollLeft:function(){return this._getScroll()[1];},_getScrollTop:function(){return this._getScroll()[0];},_getScroll:function(){var K=document.documentElement,L=document.body;if(K&&(K.scrollTop||K.scrollLeft)){return[K.scrollTop,K.scrollLeft];}else{if(L){return[L.scrollTop,L.scrollLeft];}else{return[0,0];}}},regCE:function(){},_simpleAdd:function(){if(window.addEventListener){return function(M,N,L,K){M.addEventListener(N,L,(K));};}else{if(window.attachEvent){return function(M,N,L,K){M.attachEvent("on"+N,L);};}else{return function(){};}}}(),_simpleRemove:function(){if(window.removeEventListener){return function(M,N,L,K){M.removeEventListener(N,L,(K));};}else{if(window.detachEvent){return function(L,M,K){L.detachEvent("on"+M,K);};}else{return function(){};}}}()};}();(function(){var EU=YAHOO.util.Event;EU.on=EU.addListener;EU.onFocus=EU.addFocusListener;EU.onBlur=EU.addBlurListener; /* DOMReady: based on work by: Dean Edwards/John Resig/Matthias Miller/Diego Perini */ if(EU.isIE){if(self!==self.top){document.onreadystatechange=function(){if(document.readyState=="complete"){document.onreadystatechange=null;EU._ready();}};}else{YAHOO.util.Event.onDOMReady(YAHOO.util.Event._tryPreloadAttach,YAHOO.util.Event,true);var n=document.createElement("p");EU._dri=setInterval(function(){try{n.doScroll("left");clearInterval(EU._dri);EU._dri=null;EU._ready();n=null;}catch(ex){}},EU.POLL_INTERVAL);}}else{if(EU.webkit&&EU.webkit<525){EU._dri=setInterval(function(){var rs=document.readyState;if("loaded"==rs||"complete"==rs){clearInterval(EU._dri);EU._dri=null;EU._ready();}},EU.POLL_INTERVAL);}else{EU._simpleAdd(document,"DOMContentLoaded",EU._ready);}}EU._simpleAdd(window,"load",EU._load);EU._simpleAdd(window,"unload",EU._unload);EU._tryPreloadAttach();})();}YAHOO.util.EventProvider=function(){};YAHOO.util.EventProvider.prototype={__yui_events:null,__yui_subscribers:null,subscribe:function(A,C,F,E){this.__yui_events=this.__yui_events||{};var D=this.__yui_events[A];if(D){D.subscribe(C,F,E);}else{this.__yui_subscribers=this.__yui_subscribers||{};var B=this.__yui_subscribers;if(!B[A]){B[A]=[];}B[A].push({fn:C,obj:F,overrideContext:E});}},unsubscribe:function(C,E,G){this.__yui_events=this.__yui_events||{};var A=this.__yui_events;if(C){var F=A[C];if(F){return F.unsubscribe(E,G);}}else{var B=true;for(var D in A){if(YAHOO.lang.hasOwnProperty(A,D)){B=B&&A[D].unsubscribe(E,G);}}return B;}return false;},unsubscribeAll:function(A){return this.unsubscribe(A); },createEvent:function(B,G){this.__yui_events=this.__yui_events||{};var E=G||{},D=this.__yui_events,F;if(D[B]){}else{F=new YAHOO.util.CustomEvent(B,E.scope||this,E.silent,YAHOO.util.CustomEvent.FLAT,E.fireOnce);D[B]=F;if(E.onSubscribeCallback){F.subscribeEvent.subscribe(E.onSubscribeCallback);}this.__yui_subscribers=this.__yui_subscribers||{};var A=this.__yui_subscribers[B];if(A){for(var C=0;C0){C=G.substring(0,D);A=G.substring(D,G.length);}else{C=G;}if(this._isNewSource(C)){this._createNewSource(C);}}var H=new Date();var J=new YAHOO.widget.LogMsg({msg:B,time:H,category:F,source:C,sourceDetail:A});var I=this._stack;var E=this.maxStackEntries;if(E&&!isNaN(E)&&(I.length>=E)){I.shift();}I.push(J);this.newLogEvent.fire(J);if(this._browserConsoleEnabled){this._printToBrowserConsole(J);}return true;}else{return false;}};YAHOO.widget.Logger.reset=function(){this._stack=[];this._startTime=new Date().getTime();this.loggerEnabled=true;this.log("Logger reset");this.logResetEvent.fire();};YAHOO.widget.Logger.getStack=function(){return this._stack;};YAHOO.widget.Logger.getStartTime=function(){return this._startTime;};YAHOO.widget.Logger.disableBrowserConsole=function(){YAHOO.log("Logger output to the function console.log() has been disabled.");this._browserConsoleEnabled=false;};YAHOO.widget.Logger.enableBrowserConsole=function(){this._browserConsoleEnabled=true;YAHOO.log("Logger output to the function console.log() has been enabled.");};YAHOO.widget.Logger.handleWindowErrors=function(){if(!YAHOO.widget.Logger._windowErrorsHandled){if(window.error){YAHOO.widget.Logger._origOnWindowError=window.onerror;}window.onerror=YAHOO.widget.Logger._onWindowError;YAHOO.widget.Logger._windowErrorsHandled=true;YAHOO.log("Logger handling of window.onerror has been enabled.");}else{YAHOO.log("Logger handling of window.onerror had already been enabled.");}};YAHOO.widget.Logger.unhandleWindowErrors=function(){if(YAHOO.widget.Logger._windowErrorsHandled){if(YAHOO.widget.Logger._origOnWindowError){window.onerror=YAHOO.widget.Logger._origOnWindowError;YAHOO.widget.Logger._origOnWindowError=null;}else{window.onerror=null;}YAHOO.widget.Logger._windowErrorsHandled=false;YAHOO.log("Logger handling of window.onerror has been disabled.");}else{YAHOO.log("Logger handling of window.onerror had already been disabled.");}};YAHOO.widget.Logger.categoryCreateEvent=new YAHOO.util.CustomEvent("categoryCreate",this,true);YAHOO.widget.Logger.sourceCreateEvent=new YAHOO.util.CustomEvent("sourceCreate",this,true);YAHOO.widget.Logger.newLogEvent=new YAHOO.util.CustomEvent("newLog",this,true);YAHOO.widget.Logger.logResetEvent=new YAHOO.util.CustomEvent("logReset",this,true);YAHOO.widget.Logger._createNewCategory=function(A){this.categories.push(A);this.categoryCreateEvent.fire(A);};YAHOO.widget.Logger._isNewCategory=function(B){for(var A=0;A{label} {totalTime}ms (+{elapsedTime}) {localTime}:

    {sourceAndDetail}

    {message}

    ",BASIC_TEMPLATE:"

    {label} {totalTime}ms (+{elapsedTime}) {localTime}: {sourceAndDetail}: {message}

    "});F.prototype={logReaderEnabled:true,width:null,height:null,top:null,left:null,right:null,bottom:null,fontSize:null,footerEnabled:true,verboseOutput:true,entryFormat:null,newestOnTop:true,outputBuffer:100,thresholdMax:500,thresholdMin:100,isCollapsed:false,isPaused:false,draggable:true,toString:function(){return"LogReader instance"+this._sName;},pause:function(){this.isPaused=true;this._timeout=null;this.logReaderEnabled=false;if(this._btnPause){this._btnPause.value="Resume"; }},resume:function(){this.isPaused=false;this.logReaderEnabled=true;this._printBuffer();if(this._btnPause){this._btnPause.value="Pause";}},render:function(){if(this.rendered){return;}this._initContainerEl();this._initHeaderEl();this._initConsoleEl();this._initFooterEl();this._initCategories();this._initSources();this._initDragDrop();C.newLogEvent.subscribe(this._onNewLog,this);C.logResetEvent.subscribe(this._onReset,this);C.categoryCreateEvent.subscribe(this._onCategoryCreate,this);C.sourceCreateEvent.subscribe(this._onSourceCreate,this);this.rendered=true;this._filterLogs();},destroy:function(){A.purgeElement(this._elContainer,true);this._elContainer.innerHTML="";this._elContainer.parentNode.removeChild(this._elContainer);this.rendered=false;},hide:function(){this._elContainer.style.display="none";},show:function(){this._elContainer.style.display="block";},collapse:function(){this._elConsole.style.display="none";if(this._elFt){this._elFt.style.display="none";}this._btnCollapse.value="Expand";this.isCollapsed=true;},expand:function(){this._elConsole.style.display="block";if(this._elFt){this._elFt.style.display="block";}this._btnCollapse.value="Collapse";this.isCollapsed=false;},getCheckbox:function(H){return this._filterCheckboxes[H];},getCategories:function(){return this._categoryFilters;},showCategory:function(I){var K=this._categoryFilters;if(K.indexOf){if(K.indexOf(I)>-1){return;}}else{for(var H=0;H-1){return;}}else{for(var I=0;I/g,">");}return"";},_sName:null,_buffer:null,_consoleMsgCount:0,_lastTime:null,_timeout:null,_filterCheckboxes:null,_categoryFilters:null,_sourceFilters:null,_elContainer:null,_elHd:null,_elCollapse:null,_btnCollapse:null,_title:null,_elConsole:null,_elFt:null,_elBtns:null,_elCategoryFilters:null,_elSourceFilters:null,_btnPause:null,_btnClear:null,_init:function(H,I){this._buffer=[];this._filterCheckboxes={};this._lastTime=C.getStartTime();if(I&&(I.constructor==Object)){for(var J in I){if(I.hasOwnProperty(J)){this[J]=I[J];}}}this._elContainer=E.get(H);YAHOO.log("LogReader initialized",null,this.toString());},_initContainerEl:function(){if(!this._elContainer||!/div$/i.test(this._elContainer.tagName)){this._elContainer=G.body.insertBefore(B("div"),G.body.firstChild);E.addClass(this._elContainer,"yui-log-container");}E.addClass(this._elContainer,"yui-log");var J=this._elContainer.style,H=["width","right","top","fontSize"],K,I;for(I=H.length-1;I>=0;--I){K=H[I];if(this[K]){J[K]=this[K];}}if(this.left){J.left=this.left;J.right="auto";}if(this.bottom){J.bottom=this.bottom;J.top="auto";}if(YAHOO.env.ua.opera){G.body.style+="";}},_initHeaderEl:function(){if(this._elHd){A.purgeElement(this._elHd,true);this._elHd.innerHTML="";}this._elHd=B("div",{id:"yui-log-hd"+this._sName,className:"yui-log-hd"});this._elCollapse=B("div",{className:"yui-log-btns"});this._btnCollapse=B("input",{type:"button",className:"yui-log-button",value:"Collapse"});A.on(this._btnCollapse,"click",this._onClickCollapseBtn,this);this._title=B("h4",{innerHTML:"Logger Console"});this._elCollapse.appendChild(this._btnCollapse);this._elHd.appendChild(this._elCollapse);this._elHd.appendChild(this._title);this._elContainer.appendChild(this._elHd);},_initConsoleEl:function(){if(this._elConsole){A.purgeElement(this._elConsole,true);this._elConsole.innerHTML="";}this._elConsole=B("div",{className:"yui-log-bd"});if(this.height){this._elConsole.style.height=this.height;}this._elContainer.appendChild(this._elConsole);},_initFooterEl:function(){if(this.footerEnabled){if(this._elFt){A.purgeElement(this._elFt,true);this._elFt.innerHTML="";}this._elFt=B("div",{className:"yui-log-ft"});this._elBtns=B("div",{className:"yui-log-btns"});this._btnPause=B("input",{type:"button",className:"yui-log-button",value:"Pause"});A.on(this._btnPause,"click",this._onClickPauseBtn,this);this._btnClear=B("input",{type:"button",className:"yui-log-button",value:"Clear"});A.on(this._btnClear,"click",this._onClickClearBtn,this);this._elCategoryFilters=B("div",{className:"yui-log-categoryfilters"});this._elSourceFilters=B("div",{className:"yui-log-sourcefilters"});this._elBtns.appendChild(this._btnPause);this._elBtns.appendChild(this._btnClear);this._elFt.appendChild(this._elBtns);this._elFt.appendChild(this._elCategoryFilters); this._elFt.appendChild(this._elSourceFilters);this._elContainer.appendChild(this._elFt);}},_initDragDrop:function(){if(D.DD&&this.draggable&&this._elHd){var H=new D.DD(this._elContainer);H.setHandleElId(this._elHd.id);this._elHd.style.cursor="move";}},_initCategories:function(){this._categoryFilters=[];var J=C.categories;for(var H=0;Hthis.thresholdMax)){X=0;}R=(I>X)?(I-X):0;for(O=R;O0){return YAHOO.lang.substitute(B,{message:A});}else{return A;}},fail:function(A){throw new YAHOO.util.AssertionError(this._formatMessage(A,"Test force-failed."));},areEqual:function(B,C,A){if(B!=C){throw new YAHOO.util.ComparisonFailure(this._formatMessage(A,"Values should be equal."),B,C);}},areNotEqual:function(A,C,B){if(A==C){throw new YAHOO.util.UnexpectedValue(this._formatMessage(B,"Values should not be equal."),A);}},areNotSame:function(A,C,B){if(A===C){throw new YAHOO.util.UnexpectedValue(this._formatMessage(B,"Values should not be the same."),A);}},areSame:function(B,C,A){if(B!==C){throw new YAHOO.util.ComparisonFailure(this._formatMessage(A,"Values should be the same."),B,C);}},isFalse:function(B,A){if(false!==B){throw new YAHOO.util.ComparisonFailure(this._formatMessage(A,"Value should be false."),false,B);}},isTrue:function(B,A){if(true!==B){throw new YAHOO.util.ComparisonFailure(this._formatMessage(A,"Value should be true."),true,B);}},isNaN:function(B,A){if(!isNaN(B)){throw new YAHOO.util.ComparisonFailure(this._formatMessage(A,"Value should be NaN."),NaN,B);}},isNotNaN:function(B,A){if(isNaN(B)){throw new YAHOO.util.UnexpectedValue(this._formatMessage(A,"Values should not be NaN."),NaN);}},isNotNull:function(B,A){if(YAHOO.lang.isNull(B)){throw new YAHOO.util.UnexpectedValue(this._formatMessage(A,"Values should not be null."),null);}},isNotUndefined:function(B,A){if(YAHOO.lang.isUndefined(B)){throw new YAHOO.util.UnexpectedValue(this._formatMessage(A,"Value should not be undefined."),undefined);}},isNull:function(B,A){if(!YAHOO.lang.isNull(B)){throw new YAHOO.util.ComparisonFailure(this._formatMessage(A,"Value should be null."),null,B);}},isUndefined:function(B,A){if(!YAHOO.lang.isUndefined(B)){throw new YAHOO.util.ComparisonFailure(this._formatMessage(A,"Value should be undefined."),undefined,B);}},isArray:function(B,A){if(!YAHOO.lang.isArray(B)){throw new YAHOO.util.UnexpectedValue(this._formatMessage(A,"Value should be an array."),B);}},isBoolean:function(B,A){if(!YAHOO.lang.isBoolean(B)){throw new YAHOO.util.UnexpectedValue(this._formatMessage(A,"Value should be a Boolean."),B);}},isFunction:function(B,A){if(!YAHOO.lang.isFunction(B)){throw new YAHOO.util.UnexpectedValue(this._formatMessage(A,"Value should be a function."),B);}},isInstanceOf:function(B,C,A){if(!(C instanceof B)){throw new YAHOO.util.ComparisonFailure(this._formatMessage(A,"Value isn't an instance of expected type."),B,C);}},isNumber:function(B,A){if(!YAHOO.lang.isNumber(B)){throw new YAHOO.util.UnexpectedValue(this._formatMessage(A,"Value should be a number."),B);}},isObject:function(B,A){if(!YAHOO.lang.isObject(B)){throw new YAHOO.util.UnexpectedValue(this._formatMessage(A,"Value should be an object."),B);}},isString:function(B,A){if(!YAHOO.lang.isString(B)){throw new YAHOO.util.UnexpectedValue(this._formatMessage(A,"Value should be a string."),B);}},isTypeOf:function(B,C,A){if(typeof C!=B){throw new YAHOO.util.ComparisonFailure(this._formatMessage(A,"Value should be of type "+B+"."),B,typeof C);}}};YAHOO.util.AssertionError=function(A){this.message=A;this.name="AssertionError";};YAHOO.lang.extend(YAHOO.util.AssertionError,Object,{getMessage:function(){return this.message;},toString:function(){return this.name+": "+this.getMessage();}});YAHOO.util.ComparisonFailure=function(B,A,C){YAHOO.util.AssertionError.call(this,B);this.expected=A;this.actual=C;this.name="ComparisonFailure";};YAHOO.lang.extend(YAHOO.util.ComparisonFailure,YAHOO.util.AssertionError,{getMessage:function(){return this.message+"\nExpected: "+this.expected+" ("+(typeof this.expected)+")"+"\nActual:"+this.actual+" ("+(typeof this.actual)+")";}});YAHOO.util.UnexpectedValue=function(B,A){YAHOO.util.AssertionError.call(this,B);this.unexpected=A;this.name="UnexpectedValue";};YAHOO.lang.extend(YAHOO.util.UnexpectedValue,YAHOO.util.AssertionError,{getMessage:function(){return this.message+"\nUnexpected: "+this.unexpected+" ("+(typeof this.unexpected)+") ";}});YAHOO.util.ShouldFail=function(A){YAHOO.util.AssertionError.call(this,A||"This test should fail but didn't.");this.name="ShouldFail";};YAHOO.lang.extend(YAHOO.util.ShouldFail,YAHOO.util.AssertionError);YAHOO.util.ShouldError=function(A){YAHOO.util.AssertionError.call(this,A||"This test should have thrown an error but didn't.");this.name="ShouldError";};YAHOO.lang.extend(YAHOO.util.ShouldError,YAHOO.util.AssertionError);YAHOO.util.UnexpectedError=function(A){YAHOO.util.AssertionError.call(this,"Unexpected error: "+A.message); this.cause=A;this.name="UnexpectedError";this.stack=A.stack;};YAHOO.lang.extend(YAHOO.util.UnexpectedError,YAHOO.util.AssertionError);YAHOO.util.ArrayAssert={contains:function(E,D,B){var C=false;var F=YAHOO.util.Assert;for(var A=0;A0){var B=YAHOO.util.Assert;B.fail(B._formatMessage(A,"Array should be empty."));}},isNotEmpty:function(C,A){if(C.length===0){var B=YAHOO.util.Assert;B.fail(B._formatMessage(A,"Array should not be empty."));}},itemsAreSame:function(D,F,C){var A=Math.max(D.length,F.length||0);var E=YAHOO.util.Assert;for(var B=0;B=0;B--){if(D[B]===E){F.areEqual(A,B,F._formatMessage(C,"Value exists at index "+B+" but should be at index "+A+"."));return;}}F.fail(F._formatMessage(C,"Value doesn't exist in array."));}};YAHOO.namespace("util");YAHOO.util.ObjectAssert={propertiesAreEqual:function(D,G,C){var F=YAHOO.util.Assert;var B=[];for(var E in D){B.push(E);}for(var A=0;A

    '+A.toUpperCase()+" "+C+"

    ";},init:function(){if(YAHOO.tool.TestRunner){this.setTestRunner(YAHOO.tool.TestRunner);}this.hideSource("global");this.hideSource("LogReader");this.hideCategory("warn");this.hideCategory("window");this.hideCategory("time");this.clearConsole();},clearTestRunner:function(){if(this._runner){this._runner.unsubscribeAll();this._runner=null;}},setTestRunner:function(A){if(this._runner){this.clearTestRunner();}this._runner=A;A.subscribe(A.TEST_PASS_EVENT,this._handleTestRunnerEvent,this,true);A.subscribe(A.TEST_FAIL_EVENT,this._handleTestRunnerEvent,this,true);A.subscribe(A.TEST_IGNORE_EVENT,this._handleTestRunnerEvent,this,true);A.subscribe(A.BEGIN_EVENT,this._handleTestRunnerEvent,this,true);A.subscribe(A.COMPLETE_EVENT,this._handleTestRunnerEvent,this,true);A.subscribe(A.TEST_SUITE_BEGIN_EVENT,this._handleTestRunnerEvent,this,true);A.subscribe(A.TEST_SUITE_COMPLETE_EVENT,this._handleTestRunnerEvent,this,true);A.subscribe(A.TEST_CASE_BEGIN_EVENT,this._handleTestRunnerEvent,this,true);A.subscribe(A.TEST_CASE_COMPLETE_EVENT,this._handleTestRunnerEvent,this,true);},_handleTestRunnerEvent:function(D){var A=YAHOO.tool.TestRunner;var C="";var B="";switch(D.type){case A.BEGIN_EVENT:C="Testing began at "+(new Date()).toString()+".";B="info";break;case A.COMPLETE_EVENT:C="Testing completed at "+(new Date()).toString()+".\nPassed:"+D.results.passed+" Failed:"+D.results.failed+" Total:"+D.results.total;B="info";break;case A.TEST_FAIL_EVENT:C=D.testName+": "+D.error.getMessage();B="fail";break;case A.TEST_IGNORE_EVENT:C=D.testName+": ignored.";B="ignore";break;case A.TEST_PASS_EVENT:C=D.testName+": passed.";B="pass";break;case A.TEST_SUITE_BEGIN_EVENT:C='Test suite "'+D.testSuite.name+'" started.';B="info";break;case A.TEST_SUITE_COMPLETE_EVENT:C='Test suite "'+D.testSuite.name+'" completed.\nPassed:'+D.results.passed+" Failed:"+D.results.failed+" Total:"+D.results.total;B="info";break;case A.TEST_CASE_BEGIN_EVENT:C='Test case "'+D.testCase.name+'" started.';B="info";break;case A.TEST_CASE_COMPLETE_EVENT:C='Test case "'+D.testCase.name+'" completed.\nPassed:'+D.results.passed+" Failed:"+D.results.failed+" Total:"+D.results.total;B="info";break;default:C="Unexpected event "+D.type;C="info";}YAHOO.log(C,B,"TestRunner");}});YAHOO.namespace("tool.TestFormat");YAHOO.tool.TestFormat.JSON=function(A){return YAHOO.lang.JSON.stringify(A);};YAHOO.tool.TestFormat.XML=function(C){var A=YAHOO.lang;var B="<"+C.type+' name="'+C.name.replace(/"/g,""").replace(/'/g,"'")+'"';if(A.isNumber(C.duration)){B+=' duration="'+C.duration+'"';}if(C.type=="test"){B+=' result="'+C.result+'" message="'+C.message+'">';}else{B+=' passed="'+C.passed+'" failed="'+C.failed+'" ignored="'+C.ignored+'" total="'+C.total+'">';for(var D in C){if(A.hasOwnProperty(C,D)&&A.isObject(C[D])&&!A.isArray(C[D])){B+=arguments.callee(C[D]);}}}B+="";return B;};YAHOO.namespace("tool");YAHOO.tool.TestReporter=function(A,B){this.url=A;this.format=B||YAHOO.tool.TestFormat.XML;this._fields=new Object();this._form=null;this._iframe=null;};YAHOO.tool.TestReporter.prototype={constructor:YAHOO.tool.TestReporter,_convertToISOString:function(A){function B(C){return C<10?"0"+C:C;}return A.getUTCFullYear()+"-"+B(A.getUTCMonth()+1)+"-"+B(A.getUTCDate())+"T"+B(A.getUTCHours())+":"+B(A.getUTCMinutes())+":"+B(A.getUTCSeconds())+"Z";},addField:function(A,B){this._fields[A]=B;},clearFields:function(){this._fields=new Object();},destroy:function(){if(this._form){this._form.parentNode.removeChild(this._form);this._form=null;}if(this._iframe){this._iframe.parentNode.removeChild(this._iframe);this._iframe=null;}this._fields=null;},report:function(A){if(!this._form){this._form=document.createElement("form");this._form.method="post";this._form.style.visibility="hidden";this._form.style.position="absolute";this._form.style.top=0;document.body.appendChild(this._form);if(YAHOO.env.ua.ie){this._iframe=document.createElement('