├── .atom └── launches │ ├── all.yaml │ ├── generate_analysis.yaml │ ├── grind.yaml │ ├── source_map.yaml │ ├── test.yaml │ └── utils_test.yaml ├── .gitattributes ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── assets └── dart-plugin-atom-screenshot.png ├── coffeelint.json ├── doc ├── getting_started.md └── images │ └── screenshot.png ├── grammars ├── dart.cson ├── yaml-ext-license.md └── yaml-ext.cson ├── images ├── gear.svg └── loading.svg ├── keymaps └── atom-dart.cson ├── lib ├── analysis │ ├── analysis_options.dart │ ├── buffer_observer.dart │ ├── completions.dart │ ├── dartdoc.dart │ ├── declaration_nav.dart │ ├── find_type.dart │ ├── formatting.dart │ ├── organize_file.dart │ ├── quick_fixes.dart │ ├── refactor.dart │ ├── references.dart │ └── type_hierarchy.dart ├── analysis_server.dart ├── atom_autocomplete.dart ├── atom_linter.dart ├── atom_package_deps.dart ├── atom_statusbar.dart ├── atom_treeview.dart ├── atom_utils.dart ├── browser.dart ├── dartino │ ├── dartino.dart │ ├── dartino_project_settings.dart │ ├── dartino_util.dart │ ├── device │ │ ├── dartuino_board.dart │ │ ├── device.dart │ │ ├── local_device.dart │ │ └── stm32f.dart │ ├── launch_dartino.dart │ └── sdk │ │ ├── dartino_sdk.dart │ │ └── sdk.dart ├── debug │ ├── breakpoints.dart │ ├── chrome.dart │ ├── chrome_debugger.dart │ ├── debugger.dart │ ├── debugger_tooltip.dart │ ├── debugger_ui.dart │ ├── evaluator.dart │ ├── model.dart │ ├── observatory.dart │ ├── observatory_debugger.dart │ ├── utils.dart │ └── websocket.dart ├── editors.dart ├── elements.dart ├── error_repository.dart ├── flutter │ ├── flutter.dart │ ├── flutter_connect.dart │ ├── flutter_daemon.dart │ ├── flutter_devices.dart │ ├── flutter_ext.dart │ ├── flutter_launch.dart │ ├── flutter_sdk.dart │ ├── flutter_tools.dart │ ├── flutter_ui.dart │ └── mojo_launch.dart ├── impl │ ├── changelog.dart │ ├── debounce.dart │ ├── editing.dart │ ├── navigation.dart │ ├── outline.dart │ ├── pub.dart │ ├── rebuild.dart │ ├── smoketest.dart │ ├── status.dart │ ├── status_display.dart │ ├── testing.dart │ ├── testing_utils.dart │ ├── toolbar.dart │ ├── toolbar_impl.dart │ └── tooltip.dart ├── jobs.dart ├── launch │ ├── console.dart │ ├── launch.dart │ ├── launch_cli.dart │ ├── launch_configs.dart │ ├── launch_serve.dart │ ├── launch_shell.dart │ ├── launch_web.dart │ └── run.dart ├── linter.dart ├── material.dart ├── plugin.dart ├── projects.dart ├── sdk.dart ├── state.dart ├── usage.dart ├── utils.dart └── views.dart ├── menus └── atom-dart.cson ├── package.json ├── pubspec.yaml ├── settings ├── dart.cson └── yaml-ext.cson ├── snippets └── dart.cson ├── spec ├── _spec │ ├── animals_test.dart │ ├── jasmine.dart │ ├── sample-spec.dart │ └── test.dart ├── all-spec.dart ├── flutter │ └── launch_flutter_test.dart ├── projects_test.dart └── sdk_test.dart ├── styles ├── console.less ├── dartdoc.less ├── dartlang.less ├── debugger.less ├── layout.css ├── material.less ├── outline.less ├── status.less ├── tooltip.less └── views.less ├── test ├── all.dart ├── dartino_util_test.dart ├── dependencies_test.dart ├── evaluator_test.dart ├── testing_utils_test.dart └── utils_test.dart ├── tool ├── grind.dart ├── source_map.dart ├── src │ ├── parser.dart │ └── src_gen.dart ├── test.dart └── travis.sh └── web ├── entry.dart ├── entry.dart.js └── entry.dart.js.map /.atom/launches/all.yaml: -------------------------------------------------------------------------------- 1 | # Cli launch configuration for test/all.dart. 2 | type: cli 3 | path: test/all.dart 4 | 5 | cli: 6 | # Additional args for the application. 7 | args: 8 | # The working directory to use for the launch. 9 | cwd: 10 | # Enable or disable checked mode. 11 | checked: true 12 | # Enable or disable debugging. 13 | debug: true 14 | -------------------------------------------------------------------------------- /.atom/launches/generate_analysis.yaml: -------------------------------------------------------------------------------- 1 | # Cli launch configuration for tool/analysis/generate_analysis.dart. 2 | type: cli 3 | path: tool/analysis/generate_analysis.dart 4 | 5 | cli: 6 | args: 7 | checked: true 8 | debug: true 9 | -------------------------------------------------------------------------------- /.atom/launches/grind.yaml: -------------------------------------------------------------------------------- 1 | # Cli launch configuration for tool/grind.dart. 2 | type: cli 3 | path: tool/grind.dart 4 | 5 | cli: 6 | args: 7 | checked: true 8 | -------------------------------------------------------------------------------- /.atom/launches/source_map.yaml: -------------------------------------------------------------------------------- 1 | # Cli launch configuration for tool/source_map.dart. 2 | type: cli 3 | path: tool/source_map.dart 4 | 5 | cli: 6 | # Additional args for the application. 7 | args: 8 | # The working directory to use for the launch. 9 | cwd: 10 | # Enable or disable checked mode. 11 | checked: true 12 | # Enable or disable debugging. 13 | debug: true 14 | -------------------------------------------------------------------------------- /.atom/launches/test.yaml: -------------------------------------------------------------------------------- 1 | # Cli launch configuration for tool/test.dart. 2 | type: cli 3 | path: tool/test.dart 4 | 5 | cli: 6 | # Additional args for the application. 7 | args: 8 | # The working directory to use for the launch. 9 | cwd: 10 | # Enable or disable checked mode. 11 | checked: true 12 | # Enable or disable debugging. 13 | debug: true 14 | -------------------------------------------------------------------------------- /.atom/launches/utils_test.yaml: -------------------------------------------------------------------------------- 1 | # Cli launch configuration for test/utils_test.dart. 2 | type: cli 3 | path: test/utils_test.dart 4 | 5 | cli: 6 | args: 7 | checked: true 8 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Line endings: enforce LF in GitHub, convert to native on checkout. 2 | * text=auto 3 | *.dart text 4 | *.md text 5 | *.sh text 6 | *.yaml text 7 | *.png binary 8 | 9 | web/entry.dart.js linguist-vendored 10 | web/entry_dart_results.html linguist-documentation 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .buildlog 2 | .DS_Store 3 | .idea 4 | .packages 5 | .pub/ 6 | .settings/ 7 | build/ 8 | doc/api/ 9 | packages 10 | pubspec.lock 11 | npm-debug.log 12 | node_modules 13 | 14 | spec/all-spec.js 15 | spec/all-spec.js.map 16 | 17 | *.dart.js 18 | *.dart.js.deps 19 | *.dart.js.map 20 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: dart 2 | dart: stable 3 | 4 | script: ./tool/travis.sh 5 | 6 | branches: 7 | only: 8 | - master 9 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | First up, please join our [dart-atom-dev mailing list][list]. 2 | 3 | Contributions welcome! You can help out by testing and filing issues, helping 4 | with docs, or writing code. 5 | 6 | ## Developing the plugin 7 | To work on `dartlang` plugin: 8 | 9 | - install [Atom](https://atom.io/) 10 | - clone this repo 11 | - from the command line, run `pub get` 12 | - to use `grind` : 13 | - from the command line, run `pub global activate grinder` 14 | - then run `grind build`; this will re-compile the javascript 15 | - from the repo directory, type `apm link` (you can install `apm` via the 16 | `Atom > Install Shell Commands` menu item) 17 | - restart Atom 18 | 19 | The plugin will be active in your copy of Atom. When making changes: 20 | 21 | - `type type type...` 22 | 23 | and: 24 | 25 | - run `grind build` 26 | - from atom, hit `ctrl-option-command-l`; this will re-start atom 27 | 28 | ## Publishing a new release 29 | 30 | - `git pull` the latest 31 | - rev the changelog version from 'unreleased' to the next version 32 | - rev the pubspec version to the same 33 | - commit, push to master 34 | - verify that the package.json version is one minor patch older; apm will rev 35 | the last number - we want that to match the changelog and pubspec versions 36 | - from the CLI: `apm publish patch` 37 | 38 | ## Docs 39 | 40 | Some of our docs are in the main readme.md file. We try and keep that file short 41 | and sweet. 42 | 43 | Most of our docs are in the `gh-pages` branch on the repo. We author the docs in 44 | markdown, and github's gh-pages system automatically converts it the html (we're 45 | using the 'SinglePaged' jekyll template). Changes pushed to the `gh-pages` 46 | branch go live automatically. When working on the docs, run `jekyll serve -w 47 | --force_polling` to see a preview version of the rendered docs. 48 | 49 | [list]: https://groups.google.com/forum/#!forum/dart-atom-dev 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2015, the Dart project authors. All rights reserved. 2 | Redistribution and use in source and binary forms, with or without 3 | modification, are permitted provided that the following conditions are 4 | met: 5 | 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above 9 | copyright notice, this list of conditions and the following 10 | disclaimer in the documentation and/or other materials provided 11 | with the distribution. 12 | * Neither the name of Google Inc. nor the names of its 13 | contributors may be used to endorse or promote products derived 14 | from this software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dart plugin for Atom 2 | 3 | A [Dart](https://www.dartlang.org) plugin for [Atom](https://atom.io). 4 | 5 | ## DEPRECATED 6 | 7 | **Note**: This plugin is deprecated, and is no longer actively maintained. 8 | 9 | It's recommended that people investigate more actively maintained Dart plugins, such as those for 10 | [IntelliJ](https://dart.dev/tools/jetbrains-plugin) and 11 | [VS Code](https://dart.dev/tools/vs-code). 12 | 13 | ## Getting started 14 | 15 | See our [getting started guide](https://dart-atom.github.io/dart/)! This is 16 | useful for users new to the plugin and users new to Atom as well. 17 | 18 | ## What is it? 19 | 20 | This package is a lightweight, streamlined Dart development plugin for Atom. It 21 | supports features like auto-discovery of the Dart SDK, errors and warnings shown 22 | as you type, code completion, refactorings, debugging, and integration with Pub 23 | and other tools. 24 | 25 | ![Dart plugin for Atom screenshot](https://raw.githubusercontent.com/dart-atom/dart/master/assets/dart-plugin-atom-screenshot.png) 26 | 27 | ## Installing 28 | 29 | - install Atom [atom.io](https://atom.io/) 30 | - install this [dart][] package (with `apm install dart` or through the 31 | Atom UI) 32 | 33 | ## Terms and Privacy 34 | 35 | The Dart plugin for Atom (the "Plugin") is an open-source project, contributed 36 | to by Google. By using the Plugin, you consent to be bound by Google's general 37 | Terms of Service and Google's general 38 | [Privacy Policy](http://www.google.com/intl/en/policies/privacy/). 39 | 40 | ## License 41 | 42 | See the [LICENSE](https://github.com/dart-atom/dart/blob/master/LICENSE) 43 | file. 44 | 45 | [dart]: https://atom.io/packages/dart 46 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | analyzer: 2 | strong-mode: true 3 | linter: 4 | rules: 5 | - directives_ordering 6 | -------------------------------------------------------------------------------- /assets/dart-plugin-atom-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dart-atom/dart/b37d3f916904e6f7564fcd794288d83c137af10b/assets/dart-plugin-atom-screenshot.png -------------------------------------------------------------------------------- /coffeelint.json: -------------------------------------------------------------------------------- 1 | { 2 | "max_line_length": { 3 | "level": "ignore" 4 | }, 5 | "no_empty_param_list": { 6 | "level": "error" 7 | }, 8 | "arrow_spacing": { 9 | "level": "error" 10 | }, 11 | "no_interpolation_in_single_quotes": { 12 | "level": "error" 13 | }, 14 | "no_debugger": { 15 | "level": "error" 16 | }, 17 | "colon_assignment_spacing": { 18 | "spacing": { 19 | "left": 0, 20 | "right": 1 21 | }, 22 | "level": "error" 23 | }, 24 | "braces_spacing": { 25 | "spaces": 0, 26 | "level": "error" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /doc/getting_started.md: -------------------------------------------------------------------------------- 1 | The getting started guide has [moved](http://dart-atom.github.io/dartlang/). 2 | -------------------------------------------------------------------------------- /doc/images/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dart-atom/dart/b37d3f916904e6f7564fcd794288d83c137af10b/doc/images/screenshot.png -------------------------------------------------------------------------------- /grammars/yaml-ext-license.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 GitHub Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | -------------------------------------------------------------------- 23 | 24 | This package was derived from a TextMate bundle located at 25 | https://github.com/textmate/yaml.tmbundle and distributed under the following 26 | license, located in `README.mdown`: 27 | 28 | Permission to copy, use, modify, sell and distribute this 29 | software is granted. This software is provided "as is" without 30 | express or implied warranty, and with no claim as to its 31 | suitability for any purpose. 32 | -------------------------------------------------------------------------------- /images/gear.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /images/loading.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 9 | 10 | 11 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /keymaps/atom-dart.cson: -------------------------------------------------------------------------------- 1 | # Keybindings require three things to be fully defined: A selector that is 2 | # matched against the focused element, the keystroke and the command to execute. 3 | # For more detailed documentation see 4 | # https://atom.io/docs/latest/behind-atom-keymaps-in-depth. 5 | 6 | # Global commands 7 | 'atom-workspace': 8 | 'f5': 'dart:debug-run' 9 | 'shift-f5': 'dart:debug-terminate' 10 | 'f9': 'dart:debug-toggle-breakpoint' 11 | 'f10': 'dart:debug-step' 12 | 'f11': 'dart:debug-stepin' 13 | 'shift-f11': 'dart:debug-stepout' 14 | 15 | # Global commands (mac) 16 | 'body.platform-darwin': 17 | 'ctrl-alt-cmd-t': 'dart:run-tests' 18 | 19 | # Global commands (windows and linux) 20 | 'body.platform-win32, body.platform-linux': 21 | 'ctrl-alt-t': 'dart:run-tests' 22 | 23 | # Dart editor commands (common to all platforms) 24 | 'atom-text-editor[data-grammar~="dart"]:not([mini])': 25 | 'ctrl-1': 'dart:quick-fix' 26 | 'alt-enter': 'dart:quick-fix' 27 | 'f1': 'dart:show-dartdoc' 28 | 'f4': 'dart:type-hierarchy' 29 | 'alt-shift-i': 'dart:refactor-inline-local' 30 | 'alt-shift-l': 'dart:refactor-extract-local' 31 | 'alt-shift-r': 'dart:refactor-rename' 32 | 'enter': 'dart:newline' 33 | 34 | # Dart editor commands (mac) 35 | 'body.platform-darwin atom-text-editor[data-grammar~="dart"]:not([mini])': 36 | 'cmd-f4': 'dart:find-references' 37 | 'cmd-r': 'dart:run-application' 38 | 'cmd-shift-r': 'dart:app-full-restart' 39 | 'cmd-alt-b': 'dart:dart-format' 40 | 'cmd-alt-o': 'dart:organize-directives' 41 | 'cmd-alt-down': 'dart:jump-to-declaration' 42 | 'cmd-alt-enter': 'dart:jump-to-declaration' 43 | 'shift-cmd-t': 'dart:find-type' 44 | 45 | # Handle cmd-r for launch config files. 46 | 'body.platform-darwin atom-text-editor[data-grammar~="yaml"]:not([mini])': 47 | 'cmd-r': 'dart:run-application' 48 | 'cmd-shift-r': 'dart:app-full-restart' 49 | 50 | # Dart editor commands (windows and linux) 51 | 'body.platform-linux atom-text-editor[data-grammar~="dart"]:not([mini]), body.platform-win32 atom-text-editor[data-grammar~="dart"]:not([mini])': 52 | 'ctrl-alt-enter': 'dart:jump-to-declaration' 53 | 'ctrl-f4': 'dart:find-references' 54 | 'ctrl-r': 'dart:run-application' 55 | 'ctrl-shift-r': 'dart:app-full-restart' 56 | 'ctrl-alt-b': 'dart:dart-format' 57 | 'ctrl-alt-o': 'dart:organize-directives' 58 | 'ctrl-shift-t': 'dart:find-type' 59 | 60 | # Handle ctrl-r for launch config files. 61 | 'body.platform-linux atom-text-editor[data-grammar~="yaml"]:not([mini]), body.platform-win32 atom-text-editor[data-grammar~="yaml"]:not([mini])': 62 | 'ctrl-r': 'dart:run-application' 63 | 'ctrl-shift-r': 'dart:app-full-restart' 64 | 65 | # Dart editor commands (linux) 66 | 'body.platform-linux atom-text-editor[data-grammar~="dart"]:not([mini])': 67 | 'ctrl-alt-down': 'dart:jump-to-declaration' 68 | -------------------------------------------------------------------------------- /lib/analysis/find_type.dart: -------------------------------------------------------------------------------- 1 | import 'package:analysis_server_lib/analysis_server_lib.dart' show FindTopLevelDeclarationsResult, SearchResult, Location; 2 | import 'package:atom/atom.dart'; 3 | import 'package:atom/node/notification.dart'; 4 | import 'package:atom/node/workspace.dart'; 5 | import 'package:atom/utils/disposable.dart'; 6 | import 'package:logging/logging.dart'; 7 | 8 | import '../analysis_server.dart'; 9 | import '../state.dart'; 10 | import 'references.dart'; 11 | 12 | final Logger _logger = new Logger('find_type'); 13 | 14 | class FindTypeHelper implements Disposable { 15 | Disposables disposables = new Disposables(); 16 | 17 | String _lastSearchTerm; 18 | 19 | FindTypeHelper() { 20 | disposables.add(atom.commands.add( 21 | 'atom-text-editor', 'dart:find-type', (event) => _handleFindType(event.editor) 22 | )); 23 | } 24 | 25 | void _handleFindType(TextEditor editor) { 26 | promptUser( 27 | 'Find type:', 28 | defaultText: _lastSearchTerm, 29 | selectText: true, 30 | isDart: true 31 | ).then((String searchTerm) { 32 | try { 33 | // Focus the current editor. 34 | editor?.getElement()?.focused(); 35 | } catch (e) { 36 | _logger.info('Error focusing editor in _handleFindType', e); 37 | } 38 | 39 | // Abort if user cancels the operation or there's nothing to do. 40 | if (searchTerm == null) return; 41 | 42 | searchTerm = searchTerm.trim(); 43 | if (searchTerm.isEmpty) { 44 | atom.beep(); 45 | return; 46 | } 47 | 48 | _lastSearchTerm = searchTerm; 49 | 50 | AnalysisRequestJob job = new AnalysisRequestJob('Find type', () async { 51 | String term = _createInsensitiveRegex(searchTerm); 52 | FindTopLevelDeclarationsResult result = 53 | await analysisServer.server.search.findTopLevelDeclarations(term); 54 | 55 | if (result?.id == null) { 56 | atom.beep(); 57 | return; 58 | } 59 | 60 | List results = 61 | await analysisServer.getSearchResults(result.id); 62 | 63 | if (results.isEmpty) { 64 | atom.beep(); 65 | return; 66 | } 67 | 68 | List exact = results.where((SearchResult result) { 69 | return result.path.first.name.toLowerCase() == searchTerm.toLowerCase(); 70 | }); 71 | 72 | if (exact.length == 1) { 73 | Location location = exact.first.location; 74 | navigationManager.jumpToLocation(location.file, 75 | location.startLine - 1, location.startColumn - 1, location.length); 76 | } else { 77 | deps[FindReferencesHelper].openView(new ReferencesSearch('Find Type', 78 | searchTerm, results: results)); 79 | } 80 | }); 81 | job.schedule(); 82 | }); 83 | } 84 | 85 | String _createInsensitiveRegex(String searchTerm) { 86 | StringBuffer buf = new StringBuffer(); 87 | 88 | for (int i = 0; i < searchTerm.length; i++) { 89 | String s = searchTerm[i]; 90 | buf.write('[${s.toLowerCase()}${s.toUpperCase()}]'); 91 | } 92 | 93 | return buf.toString(); 94 | } 95 | 96 | void dispose() => disposables.dispose(); 97 | } 98 | -------------------------------------------------------------------------------- /lib/analysis/formatting.dart: -------------------------------------------------------------------------------- 1 | library atom.formatting; 2 | 3 | import 'dart:async'; 4 | 5 | import 'package:atom/atom.dart'; 6 | import 'package:atom/node/process.dart'; 7 | import 'package:atom/node/workspace.dart'; 8 | import 'package:atom/utils/disposable.dart'; 9 | import 'package:logging/logging.dart'; 10 | 11 | import '../analysis_server.dart'; 12 | import '../editors.dart'; 13 | import '../state.dart'; 14 | 15 | final Logger _logger = new Logger('formatting'); 16 | 17 | // Get the current preferred line length for Dart files. 18 | int get _prefLineLength => 19 | atom.config.getValue('editor.preferredLineLength', scope: ['source.dart']); 20 | 21 | class FormattingManager implements Disposable { 22 | Disposables _commands = new Disposables(); 23 | 24 | FormattingManager() { 25 | _commands.add(atom.commands.add('.tree-view', 'dart:dart-format', (e) { 26 | formatFile(e.targetFilePath); 27 | })); 28 | _commands.add(atom.commands.add('atom-text-editor', 'dart:dart-format', (e) { 29 | formatEditor(e.editor); 30 | })); 31 | } 32 | 33 | void dispose() => _commands.dispose(); 34 | 35 | static void formatFile(String path) { 36 | if (!sdkManager.hasSdk) { 37 | atom.beep(); 38 | return; 39 | } 40 | 41 | // dartfmt -l90 -w lib/analysis/formatting.dart 42 | List args = []; 43 | args.add('-l${_prefLineLength}'); 44 | args.add('-w'); 45 | args.add(path); 46 | sdkManager.sdk.execBinSimple('dartfmt', args).then((ProcessResult result) { 47 | if (result.exit == 0) { 48 | atom.notifications.addSuccess('Formatting successful.'); 49 | } else { 50 | atom.notifications.addError('Error while formatting', description: result.stderr); 51 | } 52 | }); 53 | } 54 | 55 | /// Formats a [TextEditor]. Returns false if the editor was not formatted; 56 | /// true if it was. 57 | static Future formatEditor(TextEditor editor, {bool quiet: false}) { 58 | String path = editor.getPath(); 59 | 60 | if (projectManager.getProjectFor(path) == null) { 61 | atom.beep(); 62 | return new Future.value(false); 63 | } 64 | 65 | if (!analysisServer.isActive) { 66 | atom.beep(); 67 | return new Future.value(false); 68 | } 69 | 70 | Range range = editor.getSelectedBufferRange(); 71 | TextBuffer buffer = editor.getBuffer(); 72 | int offset = buffer.characterIndexForPosition(range.start); 73 | int end = buffer.characterIndexForPosition(range.end); 74 | 75 | // TODO: If range.isNotEmpty, just format the given selection? 76 | return analysisServer 77 | .format(path, offset, end - offset, lineLength: _prefLineLength) 78 | .then((FormatResult result) { 79 | if (result.edits.isEmpty) { 80 | if (!quiet) atom.notifications.addSuccess('No formatting changes.'); 81 | return false; 82 | } else { 83 | if (!quiet) atom.notifications.addSuccess('Formatting successful.'); 84 | applyEdits(editor, result.edits); 85 | editor.setSelectedBufferRange(new Range.fromPoints( 86 | buffer.positionForCharacterIndex(result.selectionOffset), 87 | buffer.positionForCharacterIndex( 88 | result.selectionOffset + result.selectionLength))); 89 | return true; 90 | } 91 | }).catchError((e) { 92 | if (e is RequestError) { 93 | if (!quiet) { 94 | atom.notifications 95 | .addError('Error while formatting', description: e.message); 96 | } 97 | } else { 98 | atom.beep(); 99 | _logger.warning('error when formatting: ${e}'); 100 | } 101 | return false; 102 | }); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /lib/analysis/organize_file.dart: -------------------------------------------------------------------------------- 1 | library atom.analysis.organize_file; 2 | 3 | import 'package:atom/atom.dart'; 4 | import 'package:atom/node/workspace.dart'; 5 | import 'package:atom/utils/disposable.dart'; 6 | 7 | import '../analysis_server.dart'; 8 | import '../editors.dart'; 9 | import '../state.dart'; 10 | 11 | class OrganizeFileManager implements Disposable { 12 | Disposables disposables = new Disposables(); 13 | 14 | OrganizeFileManager() { 15 | _addEditorCommand('dart:sort-members', _handleSortMembers); 16 | _addEditorCommand('dart:organize-directives', _handleOrganizeDirectives); 17 | } 18 | 19 | void _addEditorCommand(String command, void impl(TextEditor editor)) { 20 | disposables.add(atom.commands.add('atom-text-editor', command, (e) { 21 | if (!analysisServer.isActive) { 22 | atom.beep(); 23 | return; 24 | } 25 | 26 | TextEditor editor = e.editor; 27 | 28 | if (projectManager.getProjectFor(editor.getPath()) == null) { 29 | atom.beep(); 30 | return; 31 | } 32 | 33 | impl(editor); 34 | })); 35 | } 36 | 37 | void dispose() => disposables.dispose(); 38 | 39 | void _handleSortMembers(TextEditor editor) { 40 | String path = editor.getPath(); 41 | 42 | AnalysisRequestJob job = new AnalysisRequestJob('Sort members', () { 43 | return analysisServer.server.edit.sortMembers(path).then((result) { 44 | SourceFileEdit edit = result.edit; 45 | 46 | if (edit.edits.isEmpty) { 47 | atom.notifications.addSuccess('No changes from sort members.'); 48 | } else { 49 | atom.notifications.addSuccess('Sort members successful.'); 50 | applyEdits(editor, edit.edits); 51 | } 52 | }); 53 | }); 54 | 55 | job.schedule(); 56 | } 57 | 58 | void _handleOrganizeDirectives(TextEditor editor) { 59 | String path = editor.getPath(); 60 | 61 | AnalysisRequestJob job = new AnalysisRequestJob('Organize directives', () { 62 | return analysisServer.server.edit.organizeDirectives(path).then((result) { 63 | SourceFileEdit edit = result.edit; 64 | 65 | if (edit.edits.isEmpty) { 66 | atom.notifications.addSuccess('No changes from organize directives.'); 67 | } else { 68 | atom.notifications.addSuccess('Organize directives successful.'); 69 | applyEdits(editor, edit.edits); 70 | } 71 | }).catchError((e) { 72 | if (e is RequestError && e.code == 'UNKNOWN_REQUEST') { 73 | atom.notifications.addWarning( 74 | 'Organize directives is not supported in this version of the analysis server.'); 75 | } else { 76 | atom.notifications 77 | .addError('Error running organize directives.', detail: '${e}'); 78 | } 79 | }); 80 | }); 81 | 82 | job.schedule(); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /lib/analysis/quick_fixes.dart: -------------------------------------------------------------------------------- 1 | 2 | library atom.quick_fixes; 3 | 4 | import 'dart:async'; 5 | 6 | import 'package:analysis_server_lib/analysis_server_lib.dart'; 7 | import 'package:atom/atom.dart'; 8 | import 'package:atom/node/workspace.dart'; 9 | import 'package:atom/utils/disposable.dart'; 10 | import 'package:atom/utils/string_utils.dart'; 11 | 12 | import '../analysis_server.dart'; 13 | import '../atom_autocomplete.dart'; 14 | import '../editors.dart'; 15 | import '../state.dart'; 16 | 17 | class QuickFixHelper implements Disposable { 18 | Disposables disposables = new Disposables(); 19 | 20 | QuickFixHelper() { 21 | disposables.add(atom.commands.add('atom-text-editor', 22 | 'dart:quick-fix', (event) => _handleQuickFix(event.editor))); 23 | } 24 | 25 | /// Open the list of available quick fixes for the given editor at the current 26 | /// location. The editor should be visible and active. 27 | void displayQuickFixes(TextEditor editor) => 28 | _handleQuickFix(editor, autoFix: false); 29 | 30 | void dispose() => disposables.dispose(); 31 | 32 | void _handleQuickFix(TextEditor editor, {bool autoFix: true}) { 33 | String path = editor.getPath(); 34 | Range range = editor.getSelectedBufferRange(); 35 | int offset = editor.getBuffer().characterIndexForPosition(range.start); 36 | int length = editor.getBuffer().characterIndexForPosition(range.end) - offset; 37 | 38 | Job job = new AnalysisRequestJob('quick fix', () async { 39 | Future assistsFuture = analysisServer.getAssists(path, offset, length); 40 | FixesResult fixes = await analysisServer.getFixes(path, offset); 41 | AssistsResult assists = await assistsFuture; 42 | 43 | _handleFixesResult(fixes, assists, editor, autoFix: autoFix); 44 | }); 45 | job.schedule(); 46 | } 47 | 48 | void _handleFixesResult(FixesResult result, AssistsResult assists, 49 | TextEditor editor, {bool autoFix: true}) { 50 | List fixes = result.fixes; 51 | 52 | if (fixes.isEmpty && assists.assists.isEmpty) { 53 | atom.beep(); 54 | return; 55 | } 56 | 57 | List<_Change> changes = new List.from( 58 | fixes.expand((fix) => fix.fixes.map( 59 | (SourceChange change) => new _Change(change, fix.error)))); 60 | 61 | changes.addAll( 62 | assists.assists.map((SourceChange change) => new _Change(change))); 63 | 64 | if (autoFix && changes.length == 1 && assists.assists.isEmpty) { 65 | // Apply the fix. 66 | _applyChange(editor, changes.first.change); 67 | } else { 68 | int i = 0; 69 | var renderer = (_Change change) { 70 | // We need to create suggestions with unique text replacements. 71 | return new Suggestion( 72 | text: 'fix_${++i}', 73 | replacementPrefix: '', 74 | displayText: change.change.message, 75 | rightLabel: change.isAssist ? 'assist' : 'quick-fix', 76 | description: change.isAssist ? null : change.error.message, 77 | type: change.isAssist ? 'attribute' : 'function' 78 | ); 79 | }; 80 | 81 | // Show a selection dialog. 82 | chooseItemUsingCompletions(editor, changes, renderer).then((_Change choice) { 83 | editor.undo(); 84 | _applyChange(editor, choice.change); 85 | }); 86 | } 87 | } 88 | } 89 | 90 | class _Change { 91 | final SourceChange change; 92 | final AnalysisError error; 93 | 94 | _Change(this.change, [this.error]); 95 | 96 | bool get isAssist => error == null; 97 | 98 | String toString() { 99 | return error == null ? change.message : '${error.message}: ${change.message}'; 100 | } 101 | } 102 | 103 | void _applyChange(TextEditor currentEditor, SourceChange change) { 104 | List sourceFileEdits = change.edits; 105 | List linkedEditGroups = change.linkedEditGroups; 106 | 107 | Future.forEach(sourceFileEdits, (SourceFileEdit edit) { 108 | return atom.workspace.open(edit.file, 109 | options: {'searchAllPanes': true}).then((TextEditor editor) { 110 | applyEdits(editor, edit.edits); 111 | int index = sourceFileEdits.indexOf(edit); 112 | if (index >= 0 && index < linkedEditGroups.length) { 113 | selectEditGroup(editor, linkedEditGroups[index]); 114 | } 115 | }); 116 | }).then((_) { 117 | String fileSummary = sourceFileEdits.map((edit) => edit.file).join('\n'); 118 | if (sourceFileEdits.length == 1) fileSummary = null; 119 | atom.notifications.addSuccess( 120 | 'Executed quick fix: ${toStartingLowerCase(change.message)}', 121 | detail: fileSummary); 122 | 123 | // atom.workspace.open(currentEditor.getPath(), 124 | // options: {'searchAllPanes': true}).then((TextEditor editor) { 125 | // if (change.selection != null) { 126 | // editor.setCursorBufferPosition( 127 | // editor.getBuffer().positionForCharacterIndex(change.selection.offset)); 128 | // } else if (linkedEditGroups.isNotEmpty) { 129 | // selectEditGroups(currentEditor, linkedEditGroups); 130 | // } 131 | // }); 132 | }).catchError((e) { 133 | atom.notifications.addError('Error Performing Rename', detail: '${e}'); 134 | }); 135 | } 136 | -------------------------------------------------------------------------------- /lib/atom_linter.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | /// See the `linter` API [here](https://github.com/AtomLinter/linter). 6 | library atom.linter; 7 | 8 | import 'dart:async'; 9 | import 'dart:js'; 10 | 11 | import 'package:atom/atom.dart'; 12 | import 'package:atom/node/workspace.dart'; 13 | import 'package:atom/src/js.dart'; 14 | 15 | abstract class LinterProvider { 16 | final List grammarScopes; 17 | final String scope; 18 | final bool lintOnFly; 19 | 20 | final JsObject _key = jsify({'scope': 'project'}); 21 | 22 | static void registerLinterProvider( 23 | String methodName, LinterProvider provider) { 24 | final JsObject exports = context['module']['exports']; 25 | exports[methodName] = () => provider.toProxy(); 26 | } 27 | 28 | /// [grammarScopes] is a list of scopes, e.g. `['source.js', 'source.php']`. 29 | /// [scope] is one of either `file` or `project`. [lintOnFly] must be false 30 | /// for the scope `project`. 31 | LinterProvider({this.grammarScopes, this.scope, this.lintOnFly: false}); 32 | 33 | // A unique identifier for the provider; JS will store this in a hashmap as a 34 | // map key; 35 | Object get key => _key; 36 | 37 | Future> lint(TextEditor editor); 38 | 39 | JsObject toProxy() { 40 | Map map = { 41 | 'grammarScopes': grammarScopes, 42 | 'scope': scope, 43 | 'lintOnFly': lintOnFly, 44 | 'lint': _lint 45 | }; 46 | 47 | return jsify(map); 48 | } 49 | 50 | JsObject _lint(jsEditor) => jsify([]); 51 | } 52 | 53 | abstract class LinterConsumer { 54 | void consume(LinterService linterService); 55 | } 56 | 57 | class LinterService extends ProxyHolder { 58 | ProxyHolder _linter; 59 | 60 | LinterService(obj) : super(obj) { 61 | _linter = new ProxyHolder((obj as JsFunction).apply([jsify({'name': 'dart'})])); 62 | } 63 | 64 | void deleteMessages(LinterProvider provider) { 65 | _linter.invoke('clearMessages'); 66 | } 67 | 68 | void setMessages(LinterProvider provider, List messages) { 69 | var list = messages.map((m) => m.toMap()).toList(); 70 | _linter.invoke('setAllMessages', list); 71 | } 72 | } 73 | 74 | typedef void LintSolutionVoidFunc(); 75 | 76 | class LintSolution { 77 | 78 | final String title; 79 | final num priority; 80 | final Rn position; 81 | LintSolutionVoidFunc apply; 82 | 83 | LintSolution({this.title, this.priority: 0, this.position, this.apply}); 84 | 85 | Map toMap() { 86 | Map m = {}; 87 | if (title != null) m['title'] = title; 88 | if (priority != null) m['priority'] = priority; 89 | if (position != null) m['position'] = position; 90 | if (apply != null) m['apply'] = apply; 91 | return m; 92 | } 93 | } 94 | 95 | class LintMessage { 96 | static const String ERROR = 'Error'; 97 | static const String WARNING = 'Warning'; 98 | static const String INFO = 'Info'; 99 | 100 | final String type; 101 | final String text; 102 | final String correction; 103 | final String filePath; 104 | final Rn range; 105 | final bool hasFix; 106 | final List solutions; 107 | 108 | LintMessage({this.type, this.text, this.correction, this.hasFix, 109 | this.filePath, this.range, this.solutions}); 110 | 111 | Map toMap() { 112 | Map m = {}; 113 | if (type != null) m['severity'] = type.toLowerCase(); 114 | if (text != null) m['excerpt'] = text; 115 | if (correction != null) m['description'] = correction; 116 | if (filePath != null && range != null) { 117 | m['location'] = { 118 | 'file': filePath, 119 | 'position': range.toArray() 120 | }; 121 | } 122 | if (solutions != null) { 123 | m['solutions'] = solutions.map((e) => e.toMap()).toList(); 124 | } 125 | return m; 126 | } 127 | } 128 | 129 | class Rn { 130 | final Pt start; 131 | final Pt end; 132 | 133 | Rn(this.start, this.end); 134 | 135 | List toArray() => [start.toArray(), end.toArray()]; 136 | } 137 | 138 | class Pt { 139 | final int row; 140 | final int column; 141 | 142 | Pt(this.row, this.column); 143 | 144 | List toArray() => [row, column]; 145 | } 146 | -------------------------------------------------------------------------------- /lib/atom_package_deps.dart: -------------------------------------------------------------------------------- 1 | library atom.atom_package_deps; 2 | 3 | import 'dart:async'; 4 | 5 | import 'package:atom/atom.dart'; 6 | import 'package:atom/node/notification.dart'; 7 | import 'package:atom/node/package.dart'; 8 | import 'package:atom/node/process.dart'; 9 | import 'package:logging/logging.dart'; 10 | 11 | import 'jobs.dart'; 12 | 13 | final Logger _logger = new Logger('atom.atom_package_deps'); 14 | 15 | Future install() { 16 | return atomPackage.loadPackageJson().then((Map info) { 17 | List installedPackages = atom.packages.getAvailablePackageNames(); 18 | List requiredPackages = new List.from(info['required-packages']); 19 | 20 | if (requiredPackages == null || requiredPackages.isEmpty) { 21 | return null; 22 | } 23 | 24 | Set toInstall = new Set.from(requiredPackages); 25 | toInstall.removeAll(installedPackages); 26 | 27 | if (toInstall.isEmpty) return null; 28 | 29 | _logger.info('installing ${toInstall}'); 30 | 31 | return new _InstallJob(toInstall.toList()).schedule(); 32 | }); 33 | } 34 | 35 | class _InstallJob extends Job { 36 | final List packages; 37 | bool quitRequested = false; 38 | int errorCount = 0; 39 | 40 | _InstallJob(this.packages) : super("Installing Packages"); 41 | 42 | bool get quiet => true; 43 | 44 | Future run() { 45 | packages.sort(); 46 | 47 | Notification notification = atom.notifications.addInfo(name, 48 | detail: '', description: 'Installing…', dismissable: true); 49 | 50 | NotificationHelper helper = new NotificationHelper(notification.view); 51 | helper.setNoWrap(); 52 | helper.setRunning(); 53 | 54 | helper.appendText('Installing packages ${packages.join(', ')}.'); 55 | 56 | notification.onDidDismiss.listen((_) => quitRequested = true); 57 | 58 | return Future.forEach(packages, (String name) { 59 | return _install(helper, name); 60 | }).whenComplete(() { 61 | if (errorCount == 0) { 62 | helper.showSuccess(); 63 | helper.setSummary('Finished.'); 64 | } else { 65 | helper.showError(); 66 | helper.setSummary('Errors installing packages.'); 67 | } 68 | }); 69 | } 70 | 71 | Future _install(NotificationHelper helper, String name) { 72 | final String apm = atom.packages.getApmPath(); 73 | 74 | ProcessRunner runner = new ProcessRunner( 75 | apm, args: ['--no-color', 'install', name]); 76 | return runner.execSimple().then((ProcessResult result) { 77 | if (result.stdout != null && result.stdout.isNotEmpty) { 78 | helper.appendText(result.stdout.trim()); 79 | } 80 | if (result.stderr != null && result.stderr.isNotEmpty) { 81 | helper.appendText(result.stderr.trim(), stderr: true); 82 | } 83 | if (result.exit != 0) { 84 | errorCount++; 85 | } else { 86 | atom.packages.activatePackage(name); 87 | } 88 | }); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /lib/atom_statusbar.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | /// See the `status-bar` API [here](https://github.com/atom/status-bar). 6 | library atom.statusbar; 7 | 8 | import 'dart:js'; 9 | 10 | import 'package:atom/src/js.dart'; 11 | 12 | /// A wrapper around the `status-bar` API. 13 | class StatusBar extends ProxyHolder { 14 | StatusBar(JsObject obj) : super(obj); 15 | 16 | /// Add a tile to the left side of the status bar. Lower priority tiles are 17 | /// placed further to the left. The [item] parameter to these methods can be a 18 | /// DOM element, a jQuery object, or a model object for which a view provider 19 | /// has been registered in the the view registry. 20 | Tile addLeftTile({dynamic item, int priority}) { 21 | Map m = {'item': item}; 22 | if (priority != null) m['priority'] = priority; 23 | return new Tile(invoke('addLeftTile', m)); 24 | } 25 | 26 | /// Add a tile to the right side of the status bar. Lower priority tiles are 27 | /// placed further to the right. The [item] parameter to these methods can be 28 | /// a DOM element, a jQuery object, or a model object for which a view 29 | /// provider has been registered in the the view registry. 30 | Tile addRightTile({dynamic item, int priority}) { 31 | Map m = {'item': item}; 32 | if (priority != null) m['priority'] = priority; 33 | return new Tile(invoke('addRightTile', m)); 34 | } 35 | 36 | /// Retrieve all of the tiles on the left side of the status bar. 37 | List getLeftTiles() => new List.from(invoke('getLeftTiles').map((t) => new Tile(t))); 38 | 39 | /// Retrieve all of the tiles on the right side of the status bar. 40 | List getRightTiles() => new List.from(invoke('getRightTiles').map((t) => new Tile(t))); 41 | } 42 | 43 | class Tile extends ProxyHolder { 44 | Tile(JsObject obj) : super(obj); 45 | 46 | /// Retrieve the priority that was assigned to the Tile when it was created. 47 | int getPriority() => invoke('getPriority'); 48 | 49 | /// Retrieve the Tile's item. 50 | dynamic getItem() => invoke('getItem'); 51 | 52 | /// Remove the Tile from the status bar. 53 | void destroy() => invoke('destroy'); 54 | } 55 | -------------------------------------------------------------------------------- /lib/atom_treeview.dart: -------------------------------------------------------------------------------- 1 | library atom.treeview; 2 | 3 | import 'dart:js'; 4 | 5 | import 'package:atom/src/js.dart'; 6 | import 'package:atom/utils/disposable.dart'; 7 | 8 | // TODO: Dispatch back to the original service? 9 | 10 | abstract class FileIconsService implements Disposable { 11 | Function _onWillDeactivate; 12 | 13 | /// Returns a CSS class name to add to the file view 14 | String iconClassForPath(String path); 15 | 16 | /// An event that lets the tree view return to its default icon strategy. 17 | void onWillDeactivate(Function fn) { 18 | _onWillDeactivate = fn; 19 | } 20 | 21 | void dispose() { 22 | if (_onWillDeactivate != null) _onWillDeactivate(); 23 | } 24 | 25 | JsObject toProxy() { 26 | return jsify({ 27 | 'iconClassForPath': iconClassForPath, 28 | 'onWillDeactivate': onWillDeactivate 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/atom_utils.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library atom.atom_utils; 6 | 7 | import 'dart:async'; 8 | import 'dart:html' show Element, NodeValidator; 9 | 10 | import 'package:atom/atom.dart'; 11 | import 'package:atom/node/package.dart'; 12 | import 'package:atom/node/process.dart'; 13 | 14 | import 'state.dart'; 15 | 16 | /// Return a description of Atom, the plugin, and the OS. 17 | Future getSystemDescription({bool sdkPath: false}) async { 18 | // 'Atom 1.0.11, atom-dart 0.4.3, SDK 1.12 running on Windows.' 19 | String atomVer = atom.getVersion(); 20 | String os = isMac ? 'macos' : process.platform; 21 | String pluginVer = await atomPackage.getPackageVersion(); 22 | String sdkVer = sdkManager.hasSdk ? await sdkManager.sdk.getVersion() : null; 23 | 24 | String description = '\n\nAtom ${atomVer}, atom-dart ${pluginVer}'; 25 | if (sdkVer != null) description += ', SDK ${sdkVer}'; 26 | description += ' running on ${os}.'; 27 | 28 | if (sdkPath) { 29 | if (sdkManager.hasSdk) { 30 | description += '\nSDK at ${sdkManager.sdk.path}.'; 31 | } else { 32 | description += '\nNo SDK configured.'; 33 | } 34 | } 35 | 36 | return description; 37 | } 38 | 39 | /// A [NodeValidator] which allows everything. 40 | class PermissiveNodeValidator implements NodeValidator { 41 | bool allowsElement(Element element) => true; 42 | bool allowsAttribute(Element element, String attributeName, String value) { 43 | return true; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/browser.dart: -------------------------------------------------------------------------------- 1 | library atom.browser; 2 | 3 | import 'dart:async'; 4 | 5 | import 'package:atom/atom.dart'; 6 | import 'package:atom/node/fs.dart'; 7 | import 'package:atom/node/process.dart'; 8 | import 'package:atom/utils/disposable.dart'; 9 | 10 | import 'impl/debounce.dart'; 11 | import 'state.dart'; 12 | 13 | class BrowserManager implements Disposable { 14 | final String browserKey = '${pluginId}.browserLocation'; 15 | final List subs = []; 16 | 17 | String get browserPath => atom.config.getValue(browserKey); 18 | 19 | Browser _browser; 20 | Browser get browser => _browser; 21 | 22 | String _version; 23 | String get version => _version; 24 | 25 | StreamController _onBrowserChangeController = 26 | new StreamController.broadcast(sync: true); 27 | StreamController _onVersionChangeController = 28 | new StreamController.broadcast(sync: true); 29 | 30 | Stream get onBrowserChange => _onBrowserChangeController.stream; 31 | Stream get onBrowserVersionChange => 32 | _onVersionChangeController.stream; 33 | 34 | BrowserManager() { 35 | void update(path) { 36 | _browser = new Browser(path); 37 | _onBrowserChangeController.add(_browser); 38 | // Now get version if possible. 39 | _browser.getVersion().then((version) { 40 | _version = version; 41 | _onVersionChangeController.add(version); 42 | }); 43 | } 44 | 45 | subs.add(atom.config 46 | .onDidChange(browserKey) 47 | .transform(new Debounce(new Duration(seconds: 1))) 48 | .listen(update)); 49 | if (browserPath != null) { 50 | update(browserPath); 51 | } 52 | } 53 | 54 | void dispose() { 55 | subs.forEach((sub) => sub.cancel()); 56 | } 57 | } 58 | 59 | class Browser { 60 | static const singletonBoolParameters = const [ 61 | 'no-default-browser-check', 62 | 'no-first-run' 63 | ]; 64 | 65 | final String path; 66 | 67 | Browser(this.path); 68 | 69 | Future getVersion() { 70 | if (!fs.existsSync(path) || !fs.statSync(path).isFile()) { 71 | return new Future.value(null); 72 | } 73 | if (isWindows) { 74 | return new Future.value('windows'); 75 | } 76 | ProcessRunner runner = 77 | new ProcessRunner.underShell(path, args: ['--version']); 78 | runner.execStreaming(); 79 | 80 | StringBuffer buf = new StringBuffer(); 81 | runner.onStdout.listen((str) => buf.write(str)); 82 | 83 | return runner.onExit.then((_) => buf.toString()); 84 | } 85 | 86 | List execArgsFromYaml(yamlArgs, {List exceptKeys: const []}) { 87 | List execArgs = []; 88 | if (yamlArgs is Map) { 89 | yamlArgs.forEach((k, v) { 90 | if (exceptKeys.contains(k)) return; 91 | if (singletonBoolParameters.contains(k)) { 92 | if (v) execArgs.add('--$k'); 93 | } else { 94 | execArgs.add('--$k=$v'); 95 | } 96 | }); 97 | } 98 | return execArgs; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /lib/dartino/dartino_project_settings.dart: -------------------------------------------------------------------------------- 1 | import 'package:atom/node/fs.dart'; 2 | import 'package:yaml/yaml.dart'; 3 | 4 | const _checkDartinoProject = 'checkDartinoProject'; 5 | 6 | class DartinoProjectSettings { 7 | final Directory projectDirectory; 8 | Map _settings; 9 | 10 | DartinoProjectSettings(this.projectDirectory); 11 | 12 | /// Return `true` if the user should be prompted when checking 13 | /// to see if a project is a well formed Dartino project. 14 | bool get checkDartinoProject { 15 | return this[_checkDartinoProject] != 'false'; 16 | } 17 | 18 | void set checkDartinoProject(bool value) { 19 | this[_checkDartinoProject] = value ? 'true' : 'false'; 20 | } 21 | 22 | String operator [](String key) { 23 | if (_settings == null) { 24 | try { 25 | var parsed = loadYaml(_settingsFile.readSync(true)); 26 | _settings = parsed is Map ? parsed : {}; 27 | } catch (e) { 28 | _settings = {}; 29 | } 30 | } 31 | var value = _settings[key]; 32 | return value is String ? value : null; 33 | } 34 | 35 | void operator []=(String key, String value) { 36 | if (this[key] == value) return; 37 | if (value != null) { 38 | _settings[key] = value; 39 | } else { 40 | _settings.remove(key); 41 | } 42 | var buf = new StringBuffer(); 43 | buf.writeln('# Dartino settings'); 44 | for (String key in _settings.keys.toList()..sort()) { 45 | buf.writeln('$key: ${_settings[key]}'); 46 | } 47 | _settingsFile.create().then((_) { 48 | _settingsFile.writeSync(buf.toString()); 49 | }); 50 | } 51 | 52 | File get _settingsFile => new File.fromPath( 53 | fs.join(projectDirectory.path, '.atom', 'dartino', 'settings.yaml')); 54 | } 55 | -------------------------------------------------------------------------------- /lib/dartino/dartino_util.dart: -------------------------------------------------------------------------------- 1 | 2 | /// Return `true` if the specified packages file content 3 | /// contains references to Dartino packages. 4 | bool containsDartinoReferences(String content, String sdkPath) { 5 | if (content == null || sdkPath == null) return false; 6 | if (content.isEmpty || sdkPath.isEmpty) return false; 7 | String path = new Uri.file(sdkPath).toString(); 8 | if (!path.startsWith('file://')) return false; 9 | for (String line in content.split('\n')) { 10 | if (line.contains(path)) return true; 11 | } 12 | return false; 13 | } 14 | -------------------------------------------------------------------------------- /lib/dartino/device/dartuino_board.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | 4 | import 'package:atom/atom.dart'; 5 | import 'package:atom/node/process.dart'; 6 | 7 | import '../launch_dartino.dart'; 8 | import '../sdk/dartino_sdk.dart'; 9 | import '../sdk/sdk.dart'; 10 | import 'device.dart'; 11 | 12 | /// An Dartuino board 13 | class DartuinoBoard extends Device { 14 | /// Return a target device for the given launch or `null` if none. 15 | static Future forLaunch(Sdk sdk, DartinoLaunch launch) async { 16 | //TODO(danrubel) add Windows support 17 | 18 | if (isMac || isLinux) { 19 | String ttyPath; 20 | // Old style interaction with device via TTY 21 | var stdout = await exec('ls', ['-1', '/dev']); 22 | if (stdout == null) return null; 23 | for (String line in LineSplitter.split(stdout)) { 24 | // TODO(danrubel) move this out of the plugin into dartino 25 | // and SOD command line utilities - dartino show usb devices 26 | if (line.startsWith('tty.usb') || line.startsWith('ttyUSB')) { 27 | ttyPath = '/dev/$line'; 28 | // This board surfaces 2 tty ports... and only the 2nd one works 29 | // so continue looping to pick up the 2nd tty port 30 | } 31 | } 32 | if (ttyPath != null) return new DartuinoBoard(ttyPath); 33 | } 34 | return null; 35 | } 36 | 37 | final String ttyPath; 38 | 39 | DartuinoBoard(this.ttyPath); 40 | 41 | @override 42 | Future launchDartino(DartinoSdk sdk, DartinoLaunch launch) async { 43 | atom.notifications.addError('Dartino not yet supported on this board'); 44 | return false; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /lib/dartino/device/device.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:atom/atom.dart'; 4 | import 'package:atom/node/notification.dart'; 5 | import 'package:atom_dart/dartino/sdk/sdk.dart'; 6 | 7 | import '../dartino.dart'; 8 | import '../launch_dartino.dart'; 9 | import '../sdk/dartino_sdk.dart'; 10 | import 'dartuino_board.dart'; 11 | import 'local_device.dart'; 12 | import 'stm32f.dart'; 13 | 14 | /// The connected device on which the application is executed. 15 | abstract class Device { 16 | /// Return a target device for the given launch. 17 | /// If there is a problem or a compatible device cannot be found 18 | /// then notify the user and return `null`. 19 | static Future forLaunch(Sdk sdk, DartinoLaunch launch) async { 20 | if (dartino.devicePath == 'local') { 21 | return LocalDevice.forLaunch(sdk, launch); 22 | } 23 | Device device = await Stm32f.forLaunch(sdk, launch); 24 | if (device == null) device = await DartuinoBoard.forLaunch(sdk, launch); 25 | if (device == null) { 26 | if (dartino.devicePath.isEmpty) { 27 | atom.notifications.addError('No connected devices found.', 28 | detail: 'Please connect the device and try again.\n' 29 | ' \n' 30 | 'If the device is already connected, please set the device\n' 31 | 'path in Settings > Packages > dartino > Device Path,\n' 32 | 'and/or disconnect and reconnect the device.', 33 | buttons: [ 34 | new NotificationButton('Open settings', dartino.openSettings) 35 | ]); 36 | } else { 37 | atom.notifications.addError('Device not found', 38 | detail: 'Could not find specified device:\n' 39 | '${dartino.devicePath}\n' 40 | ' \n' 41 | 'Please connect the device and try again\n' 42 | 'or change/remove the device path in\n' 43 | 'Settings > Packages > dartino > Device Path', 44 | buttons: [ 45 | new NotificationButton('Open settings', dartino.openSettings) 46 | ]); 47 | } 48 | } 49 | return device; 50 | } 51 | 52 | /// Launch the specified application on the device and return `true`. 53 | /// If there is a problem, notify the user and return `false`. 54 | Future launchDartino(DartinoSdk sdk, DartinoLaunch launch); 55 | } 56 | -------------------------------------------------------------------------------- /lib/dartino/device/local_device.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:atom/atom.dart'; 4 | 5 | import '../launch_dartino.dart'; 6 | import '../sdk/dartino_sdk.dart'; 7 | import '../sdk/sdk.dart'; 8 | import 'device.dart'; 9 | 10 | /// A device for running a Dartino app on the local host/developer machine. 11 | class LocalDevice extends Device { 12 | /// Return a target device for the given launch or `null` if none. 13 | static Future forLaunch(Sdk sdk, DartinoLaunch launch) async { 14 | return new LocalDevice(); 15 | } 16 | 17 | @override 18 | Future launchDartino(DartinoSdk sdk, DartinoLaunch launch) async { 19 | if (!await launch.debug(sdk, null)) { 20 | atom.notifications.addError('Failed to start debug session', 21 | detail: 'Failed to start debug session on local machine.\n' 22 | '${launch.primaryResource}\n' 23 | 'See console for more.'); 24 | return false; 25 | } 26 | return true; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/dartino/device/stm32f.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | 4 | import 'package:atom/atom.dart'; 5 | import 'package:atom/node/fs.dart'; 6 | import 'package:atom/node/process.dart'; 7 | 8 | import '../launch_dartino.dart'; 9 | import '../sdk/dartino_sdk.dart'; 10 | import '../sdk/sdk.dart'; 11 | import 'device.dart'; 12 | 13 | /// An STM32F Discovery or Nucleo board 14 | class Stm32f extends Device { 15 | //TODO(danrubel) generalize STM boards and hopefully connected devices in general 16 | 17 | /// Return a target device for the given launch or `null` if none. 18 | static Future forLaunch(Sdk sdk, DartinoLaunch launch) async { 19 | //TODO(danrubel) move this into the command line utility 20 | //TODO(danrubel) add Windows support 21 | String ttyPath; 22 | String mediaPath; 23 | 24 | if (isMac || isLinux) { 25 | var stdout = await exec('ls', ['-1', '/dev']); 26 | if (stdout == null) return null; 27 | for (String line in LineSplitter.split(stdout)) { 28 | // TODO(danrubel) move this out of the plugin into dartino 29 | // and SOD command line utilities - dartino show usb devices 30 | if (line.startsWith('tty.usb') || line.startsWith('ttyACM')) { 31 | ttyPath = '/dev/$line'; 32 | break; 33 | } 34 | } 35 | } 36 | 37 | for (String mediaName in [ 38 | 'DIS_F746NG', // STM32F746 Discovery 39 | 'NODE_F411RE', // STM32F411 Nucleo 40 | ]) { 41 | if (isMac) { 42 | mediaPath = '/Volumes/$mediaName'; 43 | } 44 | if (isLinux) { 45 | var stdout = await exec('df'); 46 | if (stdout == null) return null; 47 | for (String line in LineSplitter.split(stdout)) { 48 | if (line.endsWith('/$mediaName')) { 49 | mediaPath = line.substring(line.lastIndexOf(' /') + 1); 50 | break; 51 | } 52 | } 53 | } 54 | 55 | if (mediaPath != null || 56 | !fs.existsSync('$mediaPath/MBED.HTM') || 57 | !fs.existsSync('$mediaPath/mbed.htm')) { 58 | return new Stm32f(ttyPath, mediaPath); 59 | } 60 | } 61 | return null; 62 | } 63 | 64 | final String ttyPath; 65 | final String mediaPath; 66 | 67 | Stm32f(this.ttyPath, this.mediaPath); 68 | 69 | @override 70 | Future launchDartino(DartinoSdk sdk, DartinoLaunch launch) async { 71 | //TODO(danrubel) add windows support and move this into cmdline util 72 | if (isWindows) { 73 | atom.notifications.addError('Platform not supported'); 74 | return false; 75 | } 76 | 77 | // TODO(danrubel) use the code below 78 | // rather than `launch.launchConfiguration.debug` 79 | // because we want `debug` to default `false` 80 | // until this new feature is ready. 81 | var debug = launch.launchConfiguration.typeArgs['debug']; 82 | if (debug is! bool) debug = false; 83 | 84 | // Compile, deploy, and run 85 | var args = ['flash', launch.primaryResource]; 86 | if (debug) args.insert(1, '--debugging-mode'); 87 | var exitCode = await launch.run(sdk.dartinoBinary, 88 | args: args, 89 | message: 'Compile and deploy to connected device ...', 90 | isLast: !debug); 91 | if (exitCode != 0) { 92 | atom.notifications.addError('Failed to deploy application', 93 | detail: 'Failed to deploy to device.\n' 94 | '${launch.primaryResource}\n' 95 | 'See console for more.'); 96 | launch.launchTerminated(1, quiet: true); 97 | return false; 98 | } 99 | 100 | // If debugging then connect and start a debug session 101 | if (debug && !await launch.debug(sdk, ttyPath)) { 102 | atom.notifications.addError('Failed to start debug session', 103 | detail: 'Failed to start debug session on device.\n' 104 | '${launch.primaryResource}\n' 105 | 'See console for more.'); 106 | return false; 107 | } 108 | return true; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /lib/dartino/sdk/sdk.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:atom/atom.dart'; 4 | import 'package:atom/node/fs.dart'; 5 | import 'package:atom/node/notification.dart'; 6 | import 'package:atom/node/process.dart'; 7 | 8 | import '../launch_dartino.dart'; 9 | 10 | /// Abstract SDK implementation used by Dartino. 11 | /// Clients should call .forPath to instantiate a new SDK 12 | /// then further call [validate] to verify that the SDK is valid. 13 | abstract class Sdk { 14 | /// The root path of the sdk 15 | final String sdkRoot; 16 | 17 | Sdk(this.sdkRoot); 18 | 19 | String get name; 20 | 21 | /// Return a string representing the Sdk version, or `null` if unknown. 22 | Future get version => null; 23 | 24 | /// Return the path to the Dart SDK 25 | /// that is shipped as part of the Dartino SDK 26 | String get dartSdkPath => fs.join(sdkRoot, 'internal', 'dart-sdk'); 27 | 28 | /// Return the path to the root directory of the samples 29 | /// or `null` if none. 30 | String get samplesRoot; 31 | 32 | /// Create a new project at the specified location. 33 | /// Return a [Future] that indicates whether the project was created. 34 | Future createNewProject(String projectPath); 35 | 36 | /// Execute the given SDK binary (a command in the `bin/` folder). [cwd] can 37 | /// be either a [String] or a [Directory]. 38 | ProcessRunner execBin(String binName, List args, 39 | {cwd, bool startProcess: true}) { 40 | if (cwd is Directory) cwd = cwd.path; 41 | String osBinName = isWindows ? '${binName}.bat' : binName; 42 | String command = fs.join(sdkRoot, 'bin', osBinName); 43 | 44 | ProcessRunner runner = 45 | new ProcessRunner.underShell(command, args: args, cwd: cwd); 46 | if (startProcess) runner.execStreaming(); 47 | return runner; 48 | } 49 | 50 | /// Return `true` if the specified file exists in the SDK 51 | bool existsSync(String relativePosixPath) { 52 | var path = resolvePath(relativePosixPath); 53 | return path != null && fs.existsSync(path); 54 | } 55 | 56 | /// Return a path to the `.packages` file used to analyze the specified 57 | /// project or `null` if none, 58 | /// where [projDir] may be a [Directory] or a directory path. 59 | String packageRoot(projDir); 60 | 61 | /// Compile, deploy, and launch the specified application. 62 | /// Return a [Future] that completes when the application has been launched. 63 | Future launch(DartinoLaunch launch); 64 | 65 | /// Return the absolute OS specific path for the file or directory specified by 66 | /// [relativePosixPath] in the SDK, or `null` if there is a problem. 67 | String resolvePath(String relativePosixPath) { 68 | if (sdkRoot == null || sdkRoot.trim().isEmpty) return null; 69 | return fs.join(sdkRoot, relativePosixPath.replaceAll('/', fs.separator)); 70 | } 71 | 72 | /// Return `true` if this is a valid SDK installation, 73 | /// otherwise notify the user of the problem and return `false`. 74 | /// Set `quiet: true` to supress any user notifications. 75 | bool validate({bool quiet: false}); 76 | 77 | /// Prompt the user for and return a location to install the SDK. 78 | /// The default text will be the user's home directory plus [relPosixPath] 79 | /// where [relPosixPath] is translated into an OS specific path. 80 | /// If the selected directory already exists, 81 | /// then notify the user an return `null`. 82 | static Future promptInstallPath( 83 | String sdkName, String relPosixPath) async { 84 | var relPath = relPosixPath.replaceAll('/', fs.separator); 85 | String path = await promptUser('Enter $sdkName installation path', 86 | defaultText: fs.join(fs.homedir, relPath), selectLastWord: true); 87 | if (path == null) return null; 88 | path = path.trim(); 89 | if (path.isEmpty) return null; 90 | if (fs.existsSync(path)) { 91 | atom.notifications.addError('Invalid installation location', 92 | detail: 'The installation directory already exists.\n$path'); 93 | return null; 94 | } 95 | return path; 96 | } 97 | 98 | /// If the user has not already choosen to opt into (or out of) analytics 99 | /// then prompt the user to do so. 100 | void promptOptIntoAnalytics(); 101 | 102 | /// Show documentation for the installed SDK. 103 | void showDocs(); 104 | } 105 | -------------------------------------------------------------------------------- /lib/debug/debugger_tooltip.dart: -------------------------------------------------------------------------------- 1 | library atom.debugger_tooltip; 2 | 3 | import 'dart:async'; 4 | import 'dart:math' as math; 5 | 6 | import 'package:atom/node/workspace.dart'; 7 | import 'package:atom/utils/disposable.dart'; 8 | import 'package:logging/logging.dart'; 9 | 10 | import '../analysis_server.dart'; 11 | import '../elements.dart'; 12 | import '../impl/tooltip.dart'; 13 | import '../material.dart'; 14 | import '../state.dart'; 15 | 16 | import './debugger_ui.dart'; 17 | import './evaluator.dart'; 18 | import './model.dart'; 19 | 20 | final Logger _logger = new Logger('atom.debugger_tooltip'); 21 | 22 | class DebugTooltipManager implements Disposable { 23 | EvaluatorReverseParser parser = new EvaluatorReverseParser(); 24 | 25 | DebugTooltipManager(); 26 | 27 | // This is how many characters back at most we go to get the full context. 28 | static const backwardBuffer = 160; 29 | 30 | Future check(TooltipElement tooltip) async { 31 | HoverInformation info = tooltip.info; 32 | TextEditor editor = tooltip.editor; 33 | 34 | int startOffset = math.max(0, info.offset - backwardBuffer); 35 | int endOffset = info.offset + info.length; 36 | String input = parser.reverseString(editor.getTextInBufferRange( 37 | new Range.fromPoints( 38 | editor.getBuffer().positionForCharacterIndex(startOffset), 39 | editor.getBuffer().positionForCharacterIndex(endOffset)))); 40 | 41 | for (var connection in debugManager.connections) { 42 | if (!connection.isAlive) continue; 43 | 44 | DebugVariable result; 45 | try { 46 | // Parse in reverse from cursor, then let the evaluator unparse it 47 | // adding custom context information if needed. 48 | var expression = parser.parse(input, endOffset); 49 | // Let actual debugger to the eval. 50 | result = await connection 51 | .eval(new EvalExpression(editor.getPath(), expression)); 52 | if (result == null) return; 53 | } catch (e) { 54 | _logger.warning(e); 55 | break; 56 | } 57 | 58 | // If we have anything we create the MTree to display it in the tooltip. 59 | MTree row = 60 | new MTree(new LocalTreeModel(), ExecutionTab.renderVariable) 61 | ..flex() 62 | ..toggleClass('has-debugger-data'); 63 | 64 | row.selectedItem.onChanged.listen((variable) async { 65 | if (variable == null) return; 66 | await variable.value.invokeToString(); 67 | await row.updateItem(variable); 68 | }); 69 | 70 | await row.update([result]); 71 | 72 | String clazz = 'debugger-data'; 73 | // TODO: this might change after eval of a getter 74 | if (!result?.value?.isPrimitive ?? false) clazz += ' expandable'; 75 | tooltip.expand(div(c: clazz)..add(row)); 76 | } 77 | } 78 | 79 | bool get hasOpenedConnection => debugManager.connections.any((c) => c.isAlive); 80 | 81 | void dispose() {} 82 | } 83 | -------------------------------------------------------------------------------- /lib/debug/evaluator.dart: -------------------------------------------------------------------------------- 1 | library atom.evaluator; 2 | 3 | import 'dart:async'; 4 | 5 | import 'package:petitparser/petitparser.dart'; 6 | 7 | import '../utils.dart'; 8 | 9 | class EvaluatorReverseParser { 10 | final Property endOffset = new Property(); 11 | 12 | Parser reverseContextParser; 13 | 14 | EvaluatorReverseParser() { 15 | SettableParser expression = undefined(); 16 | SettableParser index = undefined(); 17 | SettableParser ref = undefined(); 18 | SettableParser id = undefined(); 19 | 20 | Parser trim(String c) => char(c).trim(); 21 | Parser tagged(Parser p) => new TagPositionParser(p, this.endOffset); 22 | 23 | // This is the simple dart sub-grammar we handle for now, backward: 24 | // expression :: ref ('.' ref)* 25 | // backward -> (ref '.')* ref 26 | expression.set((ref & trim('.')).star() & ref); 27 | // index :: '[' expression | number ']' e 28 | // backward -> ']' expression | number '[' 29 | index.set(trim(']') & (expression | digit()) & trim('[')); 30 | // ref :: identifier [ index ] 31 | // backward -> [ index ] identifier 32 | ref.set(index.optional() & tagged(id)); 33 | 34 | id.set(((letter() | char('_')) & (letter() | digit() | char('_')).star()) 35 | .flatten()); 36 | 37 | reverseContextParser = expression; 38 | } 39 | 40 | // Put it back in flowing order. 41 | dynamic reverseResult(dynamic value) { 42 | if (value is String) { 43 | return reverseString(value); 44 | } else if (value is List) { 45 | return value.reversed.map((v) => reverseResult(v)).toList(); 46 | } else { 47 | return value; 48 | } 49 | } 50 | 51 | String reverseString(String input) => 52 | new String.fromCharCodes(input.codeUnits.reversed); 53 | 54 | dynamic parse(String input, int endOffset) { 55 | this.endOffset.value = endOffset; 56 | return reverseResult(reverseContextParser.parse(input).value); 57 | } 58 | } 59 | 60 | class EvalExpression { 61 | String filePath; 62 | 63 | /// This is the simple dart sub-grammar we handle for now: 64 | /// expression :: ref ('.' ref)* 65 | /// ref :: identifier [ index ] 66 | /// index :: '[' expression | number ']' 67 | /// expression is in a List tree generate by petitparser. 68 | dynamic expression; 69 | 70 | EvalExpression(this.filePath, this.expression); 71 | } 72 | 73 | class Evaluator { 74 | final EvalExpression expression; 75 | 76 | Evaluator(this.expression); 77 | 78 | Future eval() async => visitExpression(expression.expression); 79 | 80 | Future visitExpression(dynamic expression) async { 81 | if (expression is String) return expression; 82 | if (expression is! List || expression.isEmpty) return ''; 83 | List parts = []; 84 | parts.add(await visitFirstReference(expression[0])); 85 | for (var sub in expression[1]) { 86 | parts.add(await visitNextReference(sub)); 87 | } 88 | return parts.join(); 89 | } 90 | 91 | Future visitFirstReference(dynamic expression) async => 92 | visitReference(true, expression); 93 | 94 | Future visitNextReference(dynamic expression) async { 95 | if (expression is String) return expression; 96 | if (expression is! List || expression.isEmpty) return ''; 97 | String right = await visitReference(false, expression[1]); 98 | return '.$right'; 99 | } 100 | 101 | Future visitReference(bool first, dynamic expression) async { 102 | if (expression is String) return expression; 103 | if (expression is! List || expression.isEmpty) return ''; 104 | String left = await visitReferenceIdentifier(first, expression[0]); 105 | String right = await visitIndex(expression[1]); 106 | return '$left$right'; 107 | } 108 | 109 | Future visitReferenceIdentifier( 110 | bool first, dynamic expression) async { 111 | if (expression is String) return expression; 112 | if (expression is! List || expression.isEmpty) return ''; 113 | return mapReferenceIdentifier(first, expression[1], expression[0]); 114 | } 115 | 116 | /// For example, here we would override this in a js debugger to add 'this' 117 | /// to the first (leftmost) identifier if needed. 118 | Future mapReferenceIdentifier( 119 | bool first, int offset, String identifier) async { 120 | return identifier; 121 | } 122 | 123 | Future visitIndex(dynamic expression) async { 124 | if (expression is String) return expression; 125 | if (expression is! List || expression.isEmpty) return ''; 126 | String inner = await visitExpression(expression[1]); 127 | return '[$inner]'; 128 | } 129 | } 130 | 131 | class TagPositionParser extends DelegateParser { 132 | final Property endOffset; 133 | 134 | TagPositionParser(Parser delegate, this.endOffset) : super(delegate); 135 | 136 | @override 137 | Result parseOn(Context context) { 138 | var result = delegate.parseOn(context); 139 | if (result.isSuccess) { 140 | return result.success([ 141 | endOffset.value - context.position - result.value.length + 1, 142 | result.value 143 | ]); 144 | } else { 145 | return result; 146 | } 147 | } 148 | 149 | @override 150 | Parser copy() => new TagPositionParser(delegate, endOffset); 151 | } 152 | -------------------------------------------------------------------------------- /lib/debug/model.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import '../launch/launch.dart'; 4 | import '../utils.dart'; 5 | import './evaluator.dart'; 6 | 7 | export './evaluator.dart' show EvalExpression; 8 | 9 | abstract class DebugConnection { 10 | final Launch launch; 11 | final Property metadata = new Property(); 12 | 13 | final SelectionGroup isolates = new SelectionGroup(); 14 | 15 | DebugConnection(this.launch); 16 | 17 | List get options => []; 18 | 19 | bool get isAlive; 20 | 21 | Stream get onPaused; 22 | Stream get onResumed; 23 | 24 | // Optional 25 | Stream> get onLibrariesChanged => null; 26 | 27 | Future terminate(); 28 | 29 | Future get onTerminated; 30 | 31 | Future resume(); 32 | stepIn(); 33 | stepOver(); 34 | stepOut(); 35 | stepOverAsyncSuspension(); 36 | autoStepOver(); 37 | 38 | void dispose(); 39 | 40 | Future eval(EvalExpression expression); 41 | } 42 | 43 | abstract class DebugOption { 44 | String get label; 45 | 46 | bool get checked; 47 | set checked(bool state); 48 | } 49 | 50 | // TODO: Add an IsolateState class. 51 | 52 | /// A representation of a VM Isolate. 53 | abstract class DebugIsolate extends MItem { 54 | DebugIsolate(); 55 | 56 | String get id => name; 57 | 58 | String get name; 59 | 60 | /// Return a more human readable name for the Isolate. 61 | String get displayName => name; 62 | 63 | String get detail; 64 | 65 | bool get suspended; 66 | 67 | bool get hasFrames => frames != null && frames.isNotEmpty; 68 | 69 | List get frames; 70 | 71 | List get libraries; 72 | 73 | pause(); 74 | Future resume(); 75 | stepIn(); 76 | stepOver(); 77 | stepOut(); 78 | stepOverAsyncSuspension(); 79 | autoStepOver(); 80 | } 81 | 82 | abstract class DebugFrame extends MItem { 83 | DebugFrame(); 84 | 85 | String get id => title; 86 | 87 | String get title; 88 | 89 | bool get isSystem; 90 | bool get isExceptionFrame; 91 | 92 | List get locals; 93 | 94 | Future> resolveLocals() => new Future.value(locals); 95 | 96 | DebugLocation get location; 97 | 98 | Future eval(String expression); 99 | 100 | String toString() => title; 101 | } 102 | 103 | abstract class DebugVariable extends MItem { 104 | String get id => name; 105 | 106 | String get name; 107 | DebugValue get value; 108 | 109 | String toString() => name; 110 | } 111 | 112 | abstract class DebugValue { 113 | String get className; 114 | 115 | String get valueAsString; 116 | 117 | bool get isPrimitive; 118 | bool get isString; 119 | bool get isPlainInstance; 120 | bool get isList; 121 | bool get isMap; 122 | 123 | bool get valueIsTruncated; 124 | 125 | int get itemsLength; 126 | 127 | bool get replaceValueOnEval; 128 | 129 | String get hint { 130 | if (isString) { 131 | // We choose not to escape double quotes here; it doesn't work well visually. 132 | String str = valueAsString; 133 | return valueIsTruncated ? '"$str…' : '"$str"'; 134 | } else if (isList) { 135 | return '[ $itemsLength ]'; 136 | } else if (isMap) { 137 | return '{ $itemsLength }'; 138 | } else if (itemsLength != null) { 139 | return '$className [ $itemsLength ]'; 140 | } else if (isPlainInstance) { 141 | return className; 142 | } else { 143 | return valueAsString; 144 | } 145 | } 146 | 147 | Future> getChildren(); 148 | 149 | Future invokeToString(); 150 | 151 | String toString() => valueAsString; 152 | } 153 | 154 | abstract class DebugLocation { 155 | /// A file path. 156 | String get path; 157 | 158 | /// 1-based line number. 159 | int get line; 160 | 161 | /// 1-based column number. 162 | int get column; 163 | 164 | /// A display file path. 165 | String get displayPath; 166 | 167 | bool resolved = false; 168 | 169 | DebugLocation(); 170 | 171 | Future resolve(); 172 | 173 | String toString() => '${path} ${line}:${column}'; 174 | } 175 | 176 | abstract class DebugLibrary extends MItem implements Comparable { 177 | 178 | String get id; 179 | 180 | String get name; 181 | String get uri; 182 | 183 | String get displayUri; 184 | 185 | bool get private; 186 | 187 | DebugLocation get location; 188 | 189 | int compareTo(other) { 190 | return displayUri.compareTo(other.displayUri); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /lib/debug/observatory.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:vm_service_lib/vm_service_lib.dart'; 4 | 5 | import 'observatory_debugger.dart'; 6 | 7 | export 'observatory_debugger.dart' show ObservatoryIsolate; 8 | 9 | abstract class ServiceWrapper { 10 | VmService get service; 11 | 12 | Iterable get allIsolates; 13 | 14 | Stream get onIsolateCreated; 15 | Stream get onIsolateFinished; 16 | } 17 | -------------------------------------------------------------------------------- /lib/debug/utils.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:atom/atom.dart'; 3 | import 'package:atom/node/fs.dart'; 4 | import 'package:atom/node/workspace.dart'; 5 | 6 | /// [line] and [column] are 1-based. 7 | Range debuggerCoordsToEditorRange(int line, int column) { 8 | int l = line - 1; 9 | int c = column == null ? 0 : column - 1; 10 | 11 | return new Range.fromPoints( 12 | new Point.coords(l, c), new Point.coords(l, c + 1) 13 | ); 14 | } 15 | 16 | LineColumn editorRangeToDebuggerCoords(Range range) { 17 | Point p = range.start; 18 | return new LineColumn(p.row + 1, p.column + 1); 19 | } 20 | 21 | String getDisplayUri(String uri) { 22 | if (uri == null) return null; 23 | 24 | if (uri.startsWith('file:')) { 25 | String path = Uri.parse(uri).toFilePath(); 26 | return atom.project.relativizePath(path)[1]; 27 | } else if (fs.existsSync(uri)) { 28 | return atom.project.relativizePath(uri)[1]; 29 | } else if (uri.startsWith('packages/')) { 30 | return 'package:${uri.substring(9)}'; 31 | } 32 | 33 | return uri; 34 | } 35 | 36 | class LineColumn { 37 | final int line; 38 | final int column; 39 | 40 | LineColumn(this.line, this.column); 41 | } 42 | -------------------------------------------------------------------------------- /lib/debug/websocket.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:js'; 3 | 4 | import 'package:atom/node/node.dart'; 5 | 6 | // TODO: This class should move into `package:atom`. 7 | 8 | class WebSocket { 9 | static JsFunction _WebSocket = require('ws'); 10 | 11 | JsObject _ws; 12 | 13 | WebSocket(String url) { 14 | // TODO(devoncarew): We could also pass in the origin here. 15 | _ws = _WebSocket.apply([url]); 16 | } 17 | 18 | Stream get onOpen { 19 | StreamController controller = new StreamController.broadcast(); 20 | _ws.callMethod('on', ['open', () => controller.add(null)]); 21 | return controller.stream; 22 | } 23 | 24 | Stream get onMessage { 25 | StreamController controller = new StreamController.broadcast(); 26 | _ws.callMethod('on', ['message', (data, flags) { 27 | controller.add(new MessageEvent(data, flags)); 28 | }]); 29 | return controller.stream; 30 | } 31 | 32 | Stream get onError { 33 | StreamController controller = new StreamController.broadcast(); 34 | _ws.callMethod('on', ['error', (event) => controller.add(event)]); 35 | return controller.stream; 36 | } 37 | 38 | Stream get onClose { 39 | StreamController controller = new StreamController.broadcast(); 40 | _ws.callMethod('on', ['close', (code, message) => controller.add(code)]); 41 | return controller.stream; 42 | } 43 | 44 | void send(String data) { 45 | _ws.callMethod('send', [data]); 46 | } 47 | 48 | void close() { 49 | _ws.callMethod('close'); 50 | } 51 | } 52 | 53 | class MessageEvent { 54 | final dynamic data; 55 | final dynamic flags; 56 | 57 | MessageEvent(this.data, this.flags); 58 | } 59 | -------------------------------------------------------------------------------- /lib/error_repository.dart: -------------------------------------------------------------------------------- 1 | library atom.error_repository; 2 | 3 | import 'dart:async'; 4 | 5 | import 'package:atom/node/fs.dart'; 6 | import 'package:atom/utils/disposable.dart'; 7 | import 'package:logging/logging.dart'; 8 | 9 | import 'package:analysis_server_lib/analysis_server_lib.dart' 10 | show AnalysisErrors, AnalysisError, AnalysisFlushResults; 11 | import 'utils.dart'; 12 | 13 | final Logger _logger = new Logger('error_repository'); 14 | 15 | /// Repository of errors generated by `analysis.errors` events. 16 | /// 17 | /// One-stop shop for getting the status of errors the analyzer has reported. 18 | /// Source agonostic. 19 | class ErrorRepository implements Disposable { 20 | static const List _emptyErrors = const []; 21 | 22 | /// A collection of all known errors that the analysis_server has provided us, 23 | /// organized by filename. 24 | final Map> knownErrors = {}; 25 | 26 | final StreamSubscriptions subs = new StreamSubscriptions(); 27 | 28 | StreamController _changeController = new StreamController.broadcast(); 29 | Stream _errorStream; 30 | Stream _flushStream; 31 | 32 | ErrorRepository(); 33 | 34 | Stream get onChange => _changeController.stream; 35 | 36 | void initStreams(Stream errorStream, 37 | Stream flushStream) { 38 | this._errorStream = errorStream; 39 | this._flushStream = flushStream; 40 | 41 | subs.cancel(); 42 | 43 | subs.add(_errorStream.listen(_handleAddErrors)); 44 | subs.add(_flushStream.listen(_handleFlushErrors)); 45 | } 46 | 47 | /// Clear all known errors. This is useful for situations like when the 48 | /// analysis server goes down. 49 | void clearAll() { 50 | knownErrors.clear(); 51 | _changeController.add(null); 52 | } 53 | 54 | /// Clear all errors for files contained within the given directory. 55 | void clearForDirectory(Directory dir) { 56 | List paths = knownErrors.keys.toList(); 57 | for (String path in paths) { 58 | if (dir.contains(path)) knownErrors.remove(path); 59 | } 60 | } 61 | 62 | List getForPath(String path) => knownErrors[path]; 63 | 64 | void dispose() => subs.cancel(); 65 | 66 | void _handleAddErrors(AnalysisErrors analysisErrors) { 67 | String path = analysisErrors.file; 68 | File file = new File.fromPath(path); 69 | 70 | // We use statSync() here and not file.isFile() as File.isFile() always 71 | // returns true. 72 | if (file.existsSync() && fs.statSync(path).isFile()) { 73 | var oldErrors = knownErrors[path]; 74 | var newErrors = analysisErrors.errors; 75 | 76 | if (oldErrors == null) oldErrors = _emptyErrors; 77 | if (newErrors == null) newErrors = _emptyErrors; 78 | 79 | knownErrors[path] = analysisErrors.errors; 80 | 81 | if (!listIdentical(oldErrors, newErrors)) { 82 | _changeController.add(null); 83 | } 84 | } else { 85 | _logger.info('received an error event for a non-existent file: ${path}'); 86 | } 87 | } 88 | 89 | void _handleFlushErrors(AnalysisFlushResults analysisFlushResults) { 90 | analysisFlushResults.files.forEach(knownErrors.remove); 91 | _changeController.add(null); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /lib/flutter/flutter.dart: -------------------------------------------------------------------------------- 1 | import 'package:atom/atom.dart'; 2 | import 'package:atom_dart/sdk.dart'; 3 | import 'package:logging/logging.dart'; 4 | import 'package:pub_semver/pub_semver.dart'; 5 | 6 | final Logger _logger = new Logger('flutter'); 7 | 8 | final Flutter flutter = new Flutter(); 9 | 10 | class Flutter { 11 | static bool hasFlutterPlugin() { 12 | return atom.packages.getAvailablePackageNames().contains('flutter'); 13 | } 14 | 15 | static void setMinSdkVersion() { 16 | if (hasFlutterPlugin()) { 17 | SdkManager.minVersion = new Version.parse('1.15.0'); 18 | } 19 | } 20 | 21 | /// Called by the Flutter plugin to enable Flutter specific behavior. 22 | void enable([AtomEvent _]) { 23 | if (!hasFlutterPlugin()) return; 24 | _logger.info('Flutter features enabled'); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/flutter/flutter_connect.dart: -------------------------------------------------------------------------------- 1 | import 'package:atom/atom.dart'; 2 | import 'package:atom/node/fs.dart'; 3 | import 'package:atom/utils/disposable.dart'; 4 | import 'package:atom/utils/string_utils.dart'; 5 | 6 | import '../elements.dart'; 7 | import '../launch/launch.dart'; 8 | import '../launch/launch_configs.dart'; 9 | import '../projects.dart'; 10 | import '../state.dart'; 11 | import 'flutter_daemon.dart'; 12 | import 'flutter_devices.dart'; 13 | import 'flutter_launch.dart'; 14 | 15 | /// Connect the tools to a Flutter app that is already running on a device. 16 | class FlutterConnectManager implements Disposable { 17 | Disposables _disposables = new Disposables(); 18 | 19 | ConnectDialog connectDialog; 20 | 21 | void showConnectDialog() { 22 | if (connectDialog == null) { 23 | connectDialog = new ConnectDialog(); 24 | _disposables.add(connectDialog); 25 | } 26 | connectDialog.show(); 27 | } 28 | 29 | void dispose() => _disposables.dispose(); 30 | } 31 | 32 | class ConnectDialog implements Disposable { 33 | TitledModelDialog dialog; 34 | CoreElement _listGroup; 35 | CoreElement itemCount; 36 | 37 | ConnectDialog() { 38 | dialog = new TitledModelDialog('Connect Debugger to Remote Flutter App:', classes: 'list-dialog'); 39 | dialog.content.add([ 40 | div(c: 'select-list')..add([_listGroup = ol(c: 'list-group')]), 41 | itemCount = div(text: 'Looking for apps…') 42 | ]); 43 | } 44 | 45 | void show() { 46 | _listGroup.clear(); 47 | dialog.show(); 48 | 49 | FlutterDaemon daemon = deps[FlutterDaemonManager].daemon; 50 | FlutterDeviceManager flutterDeviceManager = deps[FlutterDeviceManager]; 51 | 52 | if (flutterDeviceManager.currentSelectedDevice == null) { 53 | dialog.hide(); 54 | atom.notifications.addInfo('No Flutter devices found.'); 55 | return; 56 | } 57 | 58 | String deviceId = flutterDeviceManager.currentSelectedDevice.id; 59 | 60 | DaemonRequestJob job = new DaemonRequestJob('Discovering Flutter Apps', () { 61 | itemCount.text = 'Looking for apps…'; 62 | 63 | return daemon.app.discover(deviceId) 64 | .then(_updateApps) 65 | .catchError((e) { 66 | itemCount.text = 'No apps detected.'; 67 | throw e; 68 | }); 69 | }); 70 | job.schedule(); 71 | } 72 | 73 | void _handleAppClick(DiscoveredApp app) { 74 | dialog.hide(); 75 | 76 | DartProject project = projectManager.getProjectFor( 77 | atom.workspace.getActiveTextEditor()?.getPath()); 78 | if (project == null) { 79 | atom.notifications.addWarning('No active project.'); 80 | return; 81 | } 82 | 83 | FlutterLaunchType launchType = launchManager.getLaunchType('flutter'); 84 | List configs = project == null ? 85 | [] : launchConfigurationManager.getConfigsForProject(project.path); 86 | 87 | if (configs.isNotEmpty) { 88 | launchType.connectToApp(project, configs.first, app.observatoryPort); 89 | } else { 90 | // Find lib/main.dart; create a launch config for it. 91 | String mainPath = fs.join(project.path, 'lib/main.dart'); 92 | if (fs.existsSync(mainPath)) { 93 | LaunchData data = new LaunchData(fs.readFileSync(mainPath)); 94 | 95 | if (launchType.canLaunch(project.path, data)) { 96 | LaunchConfiguration config = launchConfigurationManager.createNewConfig( 97 | project.path, 98 | launchType.type, 99 | 'lib/main.dart', 100 | launchType.getDefaultConfigText() 101 | ); 102 | launchType.connectToApp(project, config, app.observatoryPort); 103 | } else { 104 | atom.notifications.addWarning('The current project is not a runnable Flutter project.'); 105 | } 106 | } else { 107 | atom.notifications.addWarning('The current project is not a runnable Flutter project.'); 108 | } 109 | } 110 | } 111 | 112 | void _updateApps(List apps) { 113 | for (DiscoveredApp app in apps) { 114 | CoreElement item = li(c: 'item-container select-item') 115 | ..layoutHorizontal() 116 | ..add([ 117 | div() 118 | ..inlineBlock() 119 | ..flex() 120 | ..text = app.id 121 | ..click(() => _handleAppClick(app)) 122 | ]); 123 | _listGroup.add(item); 124 | } 125 | 126 | if (apps.isEmpty) { 127 | itemCount.text = 'No apps detected.'; 128 | } else { 129 | itemCount.text = '${apps.length} ${pluralize('app', apps.length)} detected.'; 130 | } 131 | } 132 | 133 | void dispose() => dialog.dispose(); 134 | } 135 | -------------------------------------------------------------------------------- /lib/flutter/flutter_devices.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'dart:async'; 3 | import 'dart:collection' show LinkedHashSet; 4 | 5 | import 'package:atom/atom.dart'; 6 | import 'package:atom/utils/dependencies.dart'; 7 | import 'package:atom/utils/disposable.dart'; 8 | 9 | import '../state.dart'; 10 | import 'flutter_daemon.dart'; 11 | 12 | export 'flutter_daemon.dart' show Device; 13 | 14 | class FlutterDeviceManager implements Disposable { 15 | /// Flutter run / build modes. 16 | static List runModes = [ 17 | new BuildMode('debug', startPaused: true), 18 | new BuildMode('profile'), 19 | new BuildMode('release', supportsDebugging: false) 20 | ]; 21 | 22 | StreamSubscriptions subs = new StreamSubscriptions(); 23 | 24 | StreamController _selectedController = new StreamController.broadcast(); 25 | StreamController> _devicesController = new StreamController.broadcast(); 26 | StreamController _modeController = new StreamController.broadcast(); 27 | 28 | Device _selectedDevice; 29 | LinkedHashSet _devices = new LinkedHashSet(); 30 | 31 | BuildMode _runMode = runModes.first; 32 | 33 | FlutterDeviceManager() { 34 | _updateForDaemon(_daemonManager.daemon); 35 | subs.add(_daemonManager.onDaemonAvailable.listen(_updateForDaemon)); 36 | } 37 | 38 | BuildMode get runMode => _runMode; 39 | 40 | set runMode(BuildMode mode) { 41 | _runMode = mode; 42 | _modeController.add(_runMode); 43 | } 44 | 45 | void _updateForDaemon(FlutterDaemon daemon) { 46 | if (daemon == null) { 47 | // Clear devices. 48 | _devices.clear(); 49 | _devicesController.add(devices); 50 | 51 | _validateSelection(); 52 | } else { 53 | // query devices. 54 | _daemonManager.getDevices().then((List result) { 55 | _devices.clear(); 56 | _devices.addAll(result); 57 | _devicesController.add(devices); 58 | 59 | _validateSelection(); 60 | }); 61 | 62 | // Listen for changes. 63 | subs.add(_daemonManager.onDeviceAdded.listen(_handleDeviceAdd)); 64 | subs.add(_daemonManager.onDeviceChanged.listen(_handleDeviceChanged)); 65 | subs.add(_daemonManager.onDeviceRemoved.listen(_handleDeviceRemoved)); 66 | } 67 | } 68 | 69 | bool get isManagerActive => _daemonManager.daemon != null; 70 | 71 | Stream get onManagerActiveChanged => _daemonManager.onDaemonAvailable.map((daemon) => daemon != null); 72 | 73 | Stream get onSelectedChanged => _selectedController.stream; 74 | 75 | Stream> get onDevicesChanged => _devicesController.stream; 76 | 77 | Stream get onModeChanged => _modeController.stream; 78 | 79 | Device get currentSelectedDevice => _selectedDevice; 80 | 81 | List get devices => _devices.toList(); 82 | 83 | void setSelectedDeviceIndex(int index) { 84 | if (index >= 0 && index < _devices.length) { 85 | _selectedDevice = _devices.toList()[index]; 86 | _selectedController.add(_selectedDevice); 87 | } 88 | } 89 | 90 | void dispose() { 91 | subs.cancel(); 92 | } 93 | 94 | void _handleDeviceAdd(Device device) { 95 | atom.notifications.addSuccess("Found ${device.getLabel()}."); 96 | 97 | _devices.add(device); 98 | _devicesController.add(devices); 99 | 100 | _validateSelection(); 101 | } 102 | 103 | void _handleDeviceChanged(Device device) { 104 | _devices.add(device); 105 | 106 | // If the IDs are the same, we replace the device object as the new one might 107 | // have updated information. 108 | if (_selectedDevice == device) { 109 | _selectedDevice = device; 110 | } 111 | 112 | _validateSelection(); 113 | } 114 | 115 | void _handleDeviceRemoved(Device device) { 116 | atom.notifications.addInfo("${device.getLabel()} removed."); 117 | 118 | _devices.remove(device); 119 | _devicesController.add(devices); 120 | 121 | _validateSelection(); 122 | } 123 | 124 | void _validateSelection() { 125 | Device selected = _selectedDevice; 126 | 127 | if (_devices.isEmpty) selected = null; 128 | if (!_devices.contains(selected)) selected = null; 129 | if (selected == null && _devices.isNotEmpty) selected = _devices.first; 130 | 131 | if (selected != _selectedDevice) { 132 | _selectedDevice = selected; 133 | _selectedController.add(selected); 134 | } 135 | } 136 | 137 | FlutterDaemonManager get _daemonManager => deps[FlutterDaemonManager]; 138 | } 139 | 140 | class BuildMode { 141 | final String name; 142 | final bool supportsDebugging; 143 | final bool startPaused; 144 | 145 | BuildMode(this.name, { this.supportsDebugging: true, this.startPaused: false }); 146 | 147 | String toString() => name; 148 | } 149 | -------------------------------------------------------------------------------- /lib/flutter/flutter_ext.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'dart:async'; 3 | 4 | import 'package:logging/logging.dart'; 5 | import 'package:vm_service_lib/vm_service_lib.dart'; 6 | 7 | import '../debug/observatory.dart'; 8 | import '../utils.dart'; 9 | 10 | const String _flutterPrefix = 'ext.flutter'; 11 | 12 | final Logger _logger = new Logger('atom.flutter_ext'); 13 | 14 | /// Flutter specific debugging extensions. 15 | /// 16 | /// This includes things like adjusting the `debugPaint` and `timeDilation` 17 | /// values. 18 | class FlutterExt { 19 | final ServiceWrapper serviceWrapper; 20 | final Property enabled = new Property(false); 21 | 22 | String isolateId; 23 | Set services = new Set(); 24 | 25 | Map _reapply = {}; 26 | 27 | FlutterExt(this.serviceWrapper) { 28 | _init(); 29 | } 30 | 31 | VmService get service => serviceWrapper.service; 32 | 33 | bool get isFlutter => enabled.value; 34 | 35 | Future debugPaint(bool enabled) { 36 | const String key = '$_flutterPrefix.debugPaint'; 37 | 38 | if (enabled) { 39 | _reapply[key] = () => debugPaint(true); 40 | } else { 41 | _reapply.remove(key); 42 | } 43 | 44 | return service.callServiceExtension( 45 | key, 46 | isolateId: isolateId, 47 | args: { 'enabled': enabled } 48 | ); 49 | } 50 | 51 | Future repaintRainbow(bool enabled) { 52 | const String key = '$_flutterPrefix.repaintRainbow'; 53 | 54 | if (enabled) { 55 | _reapply[key] = () => repaintRainbow(true); 56 | } else { 57 | _reapply.remove(key); 58 | } 59 | 60 | return service.callServiceExtension( 61 | key, 62 | isolateId: isolateId, 63 | args: { 'enabled': enabled } 64 | ); 65 | } 66 | 67 | Future timeDilation(double dilation) { 68 | const String key = '$_flutterPrefix.timeDilation'; 69 | 70 | if (dilation == 1.0) { 71 | _reapply.remove(key); 72 | } else { 73 | _reapply[key] = () => timeDilation(dilation); 74 | } 75 | 76 | return service.callServiceExtension( 77 | key, 78 | isolateId: isolateId, 79 | args: { 'timeDilation': dilation } 80 | ); 81 | } 82 | 83 | Future performanceOverlay(bool enabled) { 84 | const String key = '$_flutterPrefix.showPerformanceOverlay'; 85 | 86 | if (enabled) { 87 | _reapply[key] = () => performanceOverlay(true); 88 | } else { 89 | _reapply.remove(key); 90 | } 91 | 92 | return service.callServiceExtension( 93 | key, 94 | isolateId: isolateId, 95 | args: { 'enabled': enabled } 96 | ); 97 | } 98 | 99 | void _init() { 100 | serviceWrapper.allIsolates.forEach(_checkIsolate); 101 | serviceWrapper.onIsolateCreated.listen(_checkIsolate); 102 | 103 | serviceWrapper.service.onIsolateEvent.listen((Event event) { 104 | if (event.kind == EventKind.kServiceExtensionAdded) { 105 | if (event.extensionRPC.startsWith('$_flutterPrefix.')) { 106 | _registerExtension(event.isolate.id, event.extensionRPC); 107 | } 108 | } 109 | }); 110 | } 111 | 112 | void _checkIsolate(ObservatoryIsolate isolate) { 113 | if (isolate.isolate.extensionRPCs == null) return; 114 | 115 | isolate.isolate.extensionRPCs.forEach((String ext) { 116 | if (ext.startsWith('$_flutterPrefix.')) { 117 | _registerExtension(isolate.id, ext); 118 | } 119 | }); 120 | } 121 | 122 | void _registerExtension(String isolateId, String extension) { 123 | if (!isFlutter) { 124 | enabled.value = true; 125 | } 126 | 127 | this.isolateId = isolateId; 128 | 129 | _logger.finer('Found ${extension}.'); 130 | 131 | if (services.contains(extension)) { 132 | if (_reapply.containsKey(extension)) { 133 | _reapply[extension](); 134 | } 135 | } else { 136 | services.add(extension); 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /lib/flutter/flutter_tools.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:atom/atom.dart'; 3 | import 'package:atom/node/command.dart'; 4 | import 'package:atom/node/fs.dart'; 5 | import 'package:atom/node/notification.dart'; 6 | import 'package:atom/node/workspace.dart'; 7 | import 'package:atom/utils/disposable.dart'; 8 | import 'package:haikunator/haikunator.dart'; 9 | 10 | import '../projects.dart'; 11 | import '../state.dart'; 12 | import 'flutter.dart'; 13 | import 'flutter_connect.dart'; 14 | import 'flutter_devices.dart'; 15 | import 'flutter_sdk.dart'; 16 | 17 | FlutterSdkManager _flutterSdk = deps[FlutterSdkManager]; 18 | 19 | class FlutterToolsManager implements Disposable { 20 | Disposables disposables = new Disposables(); 21 | 22 | FlutterConnectManager connectManager; 23 | 24 | FlutterToolsManager() { 25 | if (Flutter.hasFlutterPlugin()) { 26 | disposables.add(atom.commands.add( 27 | 'atom-workspace', 28 | 'flutter:screenshot', 29 | _screenshot 30 | )); 31 | disposables.add(atom.commands.add( 32 | 'atom-workspace', 33 | 'flutter:create-project', 34 | _createProject 35 | )); 36 | disposables.add(atom.commands.add( 37 | 'atom-workspace', 38 | 'flutter:doctor', 39 | _doctor 40 | )); 41 | disposables.add(atom.commands.add( 42 | 'atom-workspace', 43 | 'flutter:upgrade', 44 | _upgrade 45 | )); 46 | disposables.add(atom.commands.add( 47 | 'atom-workspace', 48 | 'flutter:connect-remote-debugger', 49 | _connect 50 | )); 51 | 52 | connectManager = new FlutterConnectManager(); 53 | disposables.add(connectManager); 54 | } 55 | } 56 | 57 | void _screenshot(AtomEvent _) { 58 | DartProject project = projectManager.getProjectFor( 59 | atom.workspace.getActiveTextEditor()?.getPath()); 60 | 61 | if (project == null) { 62 | atom.notifications.addWarning('No active project.'); 63 | return; 64 | } 65 | 66 | // Find the currently selected device. 67 | FlutterDeviceManager deviceManager = deps[FlutterDeviceManager]; 68 | Device device = deviceManager.currentSelectedDevice; 69 | 70 | // flutter screenshot [-d device.id] 71 | FlutterTool flutter = _flutterSdk.sdk.flutterTool; 72 | flutter.runInJob(device == null ? ['screenshot'] : ['screenshot', '-d', device.id], 73 | title: 'Running Flutter screenshot…', 74 | cwd: project.directory.path 75 | ); 76 | } 77 | 78 | void _createProject(AtomEvent _) { 79 | if (!_flutterSdk.hasSdk) { 80 | _flutterSdk.showInstallationInfo(); 81 | return; 82 | } 83 | 84 | String projectName = Haikunator.haikunate(delimiter: '_'); 85 | String parentPath = fs.dirname(_flutterSdk.sdk.path); 86 | String projectPath = fs.join(parentPath, projectName); 87 | 88 | String _response; 89 | FlutterTool flutter = _flutterSdk.sdk.flutterTool; 90 | 91 | promptUser( 92 | 'Enter the path to the project to create:', 93 | defaultText: projectPath, 94 | selectLastWord: true 95 | ).then((String response) { 96 | _response = response; 97 | 98 | if (_response != null) { 99 | return flutter.runInJob( 100 | ['create', _response], title: 'Creating Flutter Project' 101 | ); 102 | } 103 | }).then((_) { 104 | if (_response != null) { 105 | atom.project.addPath(_response); 106 | String path = fs.join(_response, 'lib', 'main.dart'); 107 | atom.workspace.open(path).then((TextEditor editor) { 108 | // Focus the file in the files view 'tree-view:reveal-active-file'. 109 | atom.commands.dispatch( 110 | atom.views.getView(editor), 'tree-view:reveal-active-file'); 111 | }); 112 | } 113 | }); 114 | } 115 | 116 | void _upgrade(AtomEvent _) { 117 | if (!_flutterSdk.hasSdk) { 118 | _flutterSdk.showInstallationInfo(); 119 | return; 120 | } 121 | 122 | TextEditor editor = atom.workspace.getActiveTextEditor(); 123 | if (editor == null) { 124 | atom.notifications.addWarning('No active editor.'); 125 | return; 126 | } 127 | 128 | DartProject project = projectManager.getProjectFor(editor.getPath()); 129 | if (project == null) { 130 | atom.notifications.addWarning('The current project is not a Dart project.'); 131 | return; 132 | } 133 | 134 | atom.workspace.saveAll(); 135 | 136 | FlutterTool flutter = _flutterSdk.sdk.flutterTool; 137 | flutter.runInJob(['upgrade'], 138 | title: 'Running Flutter upgrade…', 139 | cwd: project.directory.path 140 | ); 141 | } 142 | 143 | void _doctor(AtomEvent _) { 144 | if (!_flutterSdk.hasSdk) { 145 | _flutterSdk.showInstallationInfo(); 146 | return; 147 | } 148 | 149 | FlutterTool flutter = _flutterSdk.sdk.flutterTool; 150 | 151 | flutter.runInJob(['doctor'], 152 | title: 'Running Flutter doctor…', 153 | cwd: _flutterSdk.sdk.path 154 | ); 155 | } 156 | 157 | void _connect(AtomEvent _) { 158 | if (!_flutterSdk.hasSdk) { 159 | _flutterSdk.showInstallationInfo(); 160 | return; 161 | } 162 | 163 | connectManager.showConnectDialog(); 164 | } 165 | 166 | void dispose() => disposables.dispose(); 167 | } 168 | -------------------------------------------------------------------------------- /lib/flutter/flutter_ui.dart: -------------------------------------------------------------------------------- 1 | 2 | import '../debug/debugger.dart'; 3 | import '../debug/observatory_debugger.dart'; 4 | import '../elements.dart'; 5 | import '../flutter/flutter_ext.dart'; 6 | 7 | // TODO(devoncarew): We need to re-do the UI for the Flutter section to better 8 | // fit more elements (like a FPS label and the current route text). 9 | 10 | class FlutterSection { 11 | final DebugConnection connection; 12 | 13 | CoreElement infoElement; 14 | bool isDebugDrawing = false; 15 | bool isRepaintRainbow = false; 16 | bool isSlowAnimations = false; 17 | bool isPerformanceOverlay = false; 18 | 19 | FlutterSection(this.connection, CoreElement element) { 20 | element.add([ 21 | div().add([ 22 | span(text: 'Flutter', c: 'overflow-hidden-ellipsis'), 23 | infoElement = span(c: 'debugger-secondary-info') 24 | ]), 25 | table()..add([ 26 | tr()..add([ 27 | td()..add([ 28 | new CoreElement('label')..add([ 29 | new CoreElement('input') 30 | ..setAttribute('type', 'checkbox') 31 | ..click(_toggleDrawing), 32 | span(text: 'Debug drawing', c: 'text-subtle') 33 | ]) 34 | ]), 35 | td()..add([ 36 | new CoreElement('label')..add([ 37 | new CoreElement('input') 38 | ..setAttribute('type', 'checkbox') 39 | ..click(_toggleRepaintRainbow), 40 | span(text: 'Repaint rainbow', c: 'text-subtle') 41 | ]) 42 | ]) 43 | ]), 44 | tr()..add([ 45 | td()..add([ 46 | new CoreElement('label')..add([ 47 | new CoreElement('input') 48 | ..setAttribute('type', 'checkbox') 49 | ..click(_togglePerformanceOverlay), 50 | span(text: 'Performance overlay', c: 'text-subtle') 51 | ]) 52 | ]), 53 | td()..add([ 54 | new CoreElement('label')..add([ 55 | new CoreElement('input') 56 | ..setAttribute('type', 'checkbox') 57 | ..click(_toggleSlowAnimations), 58 | span(text: 'Slow animations', c: 'text-subtle') 59 | ]) 60 | ]) 61 | ]) 62 | ]) 63 | ]); 64 | 65 | element.hidden(true); 66 | 67 | if (connection is ObservatoryConnection) { 68 | ObservatoryConnection obs = connection; 69 | obs.flutterExtension.enabled.observe((value) { 70 | if (value) { 71 | element.hidden(false); 72 | } 73 | }); 74 | } 75 | } 76 | 77 | FlutterExt get flutterExtension => 78 | (connection as ObservatoryConnection).flutterExtension; 79 | 80 | void _toggleDrawing() { 81 | isDebugDrawing = !isDebugDrawing; 82 | flutterExtension.debugPaint(isDebugDrawing); 83 | } 84 | 85 | void _toggleRepaintRainbow() { 86 | isRepaintRainbow = !isRepaintRainbow; 87 | flutterExtension.repaintRainbow(isRepaintRainbow); 88 | } 89 | 90 | void _toggleSlowAnimations() { 91 | isSlowAnimations = !isSlowAnimations; 92 | flutterExtension.timeDilation(isSlowAnimations ? 5.0 : 1.0); 93 | } 94 | 95 | void _togglePerformanceOverlay() { 96 | isPerformanceOverlay = !isPerformanceOverlay; 97 | flutterExtension.performanceOverlay(isPerformanceOverlay); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /lib/flutter/mojo_launch.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'dart:async'; 3 | 4 | import 'package:atom/node/fs.dart'; 5 | import 'package:atom/node/process.dart'; 6 | 7 | import '../flutter/flutter_devices.dart'; 8 | import '../launch/launch.dart'; 9 | import '../projects.dart'; 10 | import '../state.dart'; 11 | import 'flutter_sdk.dart'; 12 | 13 | FlutterSdkManager _flutterSdk = deps[FlutterSdkManager]; 14 | FlutterDeviceManager get deviceManager => deps[FlutterDeviceManager]; 15 | 16 | class MojoLaunchType extends LaunchType { 17 | static void register(LaunchManager manager) => manager.registerLaunchType(new MojoLaunchType()); 18 | 19 | MojoLaunchType() : super('mojo'); 20 | 21 | // Don't advertise the mojo launch configuration as much as the flutter one. 22 | bool canLaunch(String path, LaunchData data) => false; 23 | 24 | String getDefaultConfigText() { 25 | return 'checked: true\n# args:\n# - --mojo-path=path/to/mojo'; 26 | } 27 | 28 | _LaunchInstance _lastLaunch; 29 | 30 | Future performLaunch(LaunchManager manager, LaunchConfiguration configuration) async { 31 | String path = configuration.primaryResource; 32 | DartProject project = projectManager.getProjectFor(path); 33 | if (project == null) throw "File not in a Dart project."; 34 | 35 | if (!_flutterSdk.hasSdk) { 36 | _flutterSdk.showInstallationInfo(); 37 | throw "Unable to launch ${configuration.shortResourceName}; no Flutter SDK found."; 38 | } 39 | 40 | await _killLastLaunch(); 41 | 42 | _lastLaunch = new _LaunchInstance(project, configuration, this); 43 | return _lastLaunch.launch(); 44 | } 45 | 46 | Future _killLastLaunch() { 47 | if (_lastLaunch == null) return new Future.value(); 48 | Launch launch = _lastLaunch._launch; 49 | return launch.isTerminated ? new Future.value() : launch.kill(); 50 | } 51 | } 52 | 53 | class _LaunchInstance { 54 | final DartProject project; 55 | 56 | Launch _launch; 57 | ProcessRunner _runner; 58 | List _args; 59 | Device _device; 60 | 61 | _LaunchInstance( 62 | this.project, 63 | LaunchConfiguration configuration, 64 | MojoLaunchType launchType 65 | ) { 66 | List flutterArgs = configuration.argsAsList; 67 | 68 | _args = ['run_mojo']; 69 | 70 | var route = configuration.typeArgs['route']; 71 | if (route is String && route.isNotEmpty) { 72 | _args.add('--route'); 73 | _args.add(route); 74 | } 75 | 76 | _device = _currentSelectedDevice; 77 | if (_device != null) { 78 | _args.add('--device-id'); 79 | _args.add(_device.id); 80 | } 81 | 82 | String relPath = fs.relativize(project.path, configuration.primaryResource); 83 | if (relPath != 'lib/main.dart') { 84 | _args.add('-t'); 85 | _args.add(relPath); 86 | } 87 | 88 | _args.addAll(flutterArgs); 89 | 90 | _launch = new Launch( 91 | launchManager, 92 | launchType, 93 | configuration, 94 | configuration.shortResourceName, 95 | killHandler: _kill, 96 | cwd: project.path, 97 | title: 'flutter ${_args.join(' ')}', 98 | targetName: _device?.name 99 | ); 100 | 101 | launchManager.addLaunch(_launch); 102 | } 103 | 104 | Future launch() async { 105 | FlutterTool flutter = _flutterSdk.sdk.flutterTool; 106 | 107 | _runner = _flutter(flutter, _args, cwd: project.path); 108 | _runner.execStreaming(); 109 | _runner.onStdout.listen((String str) => _launch.pipeStdio(str)); 110 | _runner.onStderr.listen((String str) => _launch.pipeStdio(str, error: true)); 111 | _runner.onExit.then((code) => _launch.launchTerminated(code)); 112 | 113 | return _launch; 114 | } 115 | 116 | Future _kill() { 117 | if (_runner == null) { 118 | _launch.launchTerminated(1); 119 | return new Future.value(); 120 | } else { 121 | return new Future.delayed(new Duration(milliseconds: 250), () { 122 | _runner?.kill(); 123 | _runner = null; 124 | }); 125 | } 126 | } 127 | 128 | Device get _currentSelectedDevice => deviceManager.currentSelectedDevice; 129 | } 130 | 131 | ProcessRunner _flutter(FlutterTool flutter, List args, {String cwd}) { 132 | return flutter.runRaw(args, cwd: cwd, startProcess: false); 133 | } 134 | -------------------------------------------------------------------------------- /lib/impl/changelog.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'dart:async'; 3 | import 'dart:html' show HttpRequest; 4 | 5 | import 'package:atom/atom.dart'; 6 | import 'package:atom/node/fs.dart'; 7 | import 'package:atom/node/package.dart'; 8 | import 'package:atom/node/shell.dart'; 9 | import 'package:atom/utils/disposable.dart'; 10 | import 'package:logging/logging.dart'; 11 | 12 | import '../state.dart'; 13 | 14 | final Logger _logger = new Logger('changelog'); 15 | 16 | Future checkChangelog() => atomPackage.getPackageVersion().then(_checkChangelog); 17 | 18 | class ChangelogManager implements Disposable { 19 | Disposables disposables = new Disposables(); 20 | 21 | File _changeLogFile; 22 | 23 | ChangelogManager() { 24 | disposables.add(atom.commands.add('atom-workspace', '${pluginId}:release-notes', (_) { 25 | _handleReleaseNotes(); 26 | })); 27 | disposables.add(atom.commands.add('atom-workspace', '${pluginId}:getting-started', (_) { 28 | _handleGettingStarted(); 29 | })); 30 | } 31 | 32 | void _handleReleaseNotes() { 33 | Future f; 34 | 35 | if (_changeLogFile != null) { 36 | f = new Future.value(_changeLogFile); 37 | } else { 38 | f = HttpRequest 39 | .getString('atom://dart/CHANGELOG.md') 40 | .then((contents) { 41 | Directory dir = new Directory.fromPath(fs.tmpdir); 42 | _changeLogFile = dir.getFile('CHANGELOG.md'); 43 | _changeLogFile.writeSync(contents); 44 | return _changeLogFile; 45 | }); 46 | } 47 | 48 | f.then((File file) { 49 | atom.workspace.open(file.path, options: {'split': 'right'}); 50 | }); 51 | } 52 | 53 | void _handleGettingStarted() { 54 | shell.openExternal('https://dart-atom.github.io/dart/'); 55 | } 56 | 57 | void dispose() => disposables.dispose(); 58 | } 59 | 60 | void _checkChangelog(String currentVersion) { 61 | String lastVersion = atom.config.getValue('_dart._version'); 62 | 63 | if (lastVersion != currentVersion) { 64 | _logger.info("upgraded from ${lastVersion} to ${currentVersion}"); 65 | atom.config.setValue('_dart._version', currentVersion); 66 | 67 | if (lastVersion != null) { 68 | atom.notifications.addSuccess( 69 | 'Upgraded to dart plugin version ${currentVersion}.'); 70 | } 71 | 72 | // HttpRequest.getString('atom://dart/CHANGELOG.md').then((str) { 73 | // String changes; 74 | // if (lastVersion != null) { 75 | // changes = _extractVersion(str, lastVersion, inclusive: false); 76 | // } else { 77 | // changes = _extractVersion(str, currentVersion, inclusive: true); 78 | // } 79 | // if (changes != null && changes.isNotEmpty) { 80 | // atom.notifications.addSuccess( 81 | // 'Upgraded to dart plugin version ${currentVersion}.', 82 | // description: changes, 83 | // dismissable: true); 84 | // } 85 | // }); 86 | } else { 87 | _logger.info("dart version ${currentVersion}"); 88 | } 89 | } 90 | 91 | // String _extractVersion(String changelog, String last, {bool inclusive: true}) { 92 | // Version lastVersion = new Version.parse(last); 93 | // List changes = changelog.split('\n'); 94 | // Iterable itor = changes.skipWhile((line) => !line.startsWith('##')); 95 | // changes = itor.takeWhile((line) { 96 | // if (line.startsWith('## ')) { 97 | // try { 98 | // line = line.substring(3); 99 | // Version ver = new Version.parse(line); 100 | // if (inclusive) return ver >= lastVersion; 101 | // return ver > lastVersion; 102 | // } catch (_) { 103 | // return true; 104 | // } 105 | // } 106 | // return true; 107 | // }).toList(); 108 | // return changes.join('\n').trim(); 109 | // } 110 | -------------------------------------------------------------------------------- /lib/impl/debounce.dart: -------------------------------------------------------------------------------- 1 | library atom.debounce; 2 | 3 | import 'dart:async'; 4 | 5 | class Debounce implements StreamTransformer { 6 | final Duration duration; 7 | 8 | Timer _timer; 9 | 10 | Debounce(this.duration); 11 | 12 | Stream bind(Stream stream) { 13 | StreamController controller = new StreamController(); 14 | 15 | StreamSubscription sub; 16 | 17 | sub = stream.listen((T data) { 18 | _timer?.cancel(); 19 | _timer = new Timer(duration, () => controller.add(data)); 20 | }, onDone: () => sub.cancel()); 21 | 22 | return controller.stream; 23 | } 24 | 25 | void cancel() => _timer?.cancel(); 26 | } 27 | -------------------------------------------------------------------------------- /lib/impl/navigation.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:atom/atom.dart'; 4 | import 'package:atom/node/command.dart'; 5 | import 'package:atom/node/workspace.dart'; 6 | import 'package:atom/utils/disposable.dart'; 7 | import 'package:logging/logging.dart'; 8 | 9 | import '../state.dart'; 10 | import '../usage.dart' show trackCommand; 11 | 12 | final Logger _logger = new Logger('atom.navigation'); 13 | 14 | // TODO: Finish this class. 15 | 16 | class NavigationManager implements Disposable { 17 | Disposables _commands = new Disposables(); 18 | 19 | StreamController _navController = 20 | new StreamController.broadcast(); 21 | List _history = []; 22 | List _future = []; 23 | 24 | NavigationManager() { 25 | _commands.add(atom.commands.add('atom-text-editor', 26 | 'dart:return-from-declaration', _handleNavigateReturn)); 27 | _commands.add(atom.commands.add('atom-text-editor[data-grammar~="dart"]', 28 | 'symbols-view:return-from-declaration', _handleNavigateReturn)); 29 | } 30 | 31 | // TODO: 32 | Stream get onNavigate => _navController.stream; 33 | 34 | Future jumpToLocation( 35 | String path, [int line, int column, int length] 36 | ) { 37 | _pushCurrentLocation(); 38 | return editorManager.jumpToLocation(path, line, column, length); 39 | } 40 | 41 | void goBack() { 42 | if (_history.isNotEmpty) { 43 | // TODO: 44 | 45 | } 46 | } 47 | 48 | void goForward() { 49 | if (_future.isNotEmpty) { 50 | 51 | } 52 | } 53 | 54 | bool canGoBack() => _history.isNotEmpty; 55 | 56 | bool canGoForward() => _future.isNotEmpty; 57 | 58 | void _handleNavigateReturn(AtomEvent _) { 59 | // TODO: rework this 60 | 61 | trackCommand('return-from-declaration'); 62 | 63 | if (_history.isEmpty) { 64 | _beep(); 65 | _logger.info('No navigation positions on the stack.'); 66 | } else { 67 | NavigationPosition pos = _history.removeLast(); 68 | editorManager.jumpToLocation(pos.path, pos.line, pos.column, pos.length); 69 | } 70 | } 71 | 72 | void _pushCurrentLocation() { 73 | TextEditor editor = atom.workspace.getActiveTextEditor(); 74 | 75 | if (editor != null) { 76 | Range range = editor.getSelectedBufferRange(); 77 | if (range == null) return; 78 | 79 | int length = range.isSingleLine() ? range.end.column - range.start.column : null; 80 | if (length == 0) length = null; 81 | 82 | Point start = range.start; 83 | _history.add( 84 | new NavigationPosition(editor.getPath(), start.row, start.column, length)); 85 | } 86 | } 87 | 88 | void _beep() => atom.beep(); 89 | 90 | void dispose() => _commands.dispose(); 91 | } 92 | 93 | class NavigationPosition { 94 | final String path; 95 | final int line; 96 | final int column; 97 | final int length; 98 | 99 | NavigationPosition(this.path, this.line, this.column, [this.length]); 100 | 101 | String toString() => '[${path} ${line}:${column}]'; 102 | } 103 | -------------------------------------------------------------------------------- /lib/impl/rebuild.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | /// A library used for rebuilding the `dart` project. 6 | library atom.rebuild; 7 | 8 | import 'dart:async'; 9 | 10 | import 'package:atom/atom.dart'; 11 | import 'package:atom/node/fs.dart'; 12 | import 'package:atom/utils/disposable.dart'; 13 | 14 | import '../jobs.dart'; 15 | import '../state.dart'; 16 | import 'pub.dart'; 17 | 18 | class RebuildManager implements Disposable { 19 | Disposables disposables = new Disposables(); 20 | 21 | RebuildManager() { 22 | disposables.add(atom.commands 23 | .add('atom-workspace', 'dart:rebuild-restart-dev', (_) { 24 | if (_projectsToBuild().isNotEmpty) { 25 | new RebuildJob("Rebuilding Atom plugins").schedule(); 26 | } 27 | })); 28 | } 29 | 30 | void dispose() => disposables.dispose(); 31 | } 32 | 33 | class RebuildJob extends Job { 34 | RebuildJob(String title) : super(title, RebuildJob); 35 | 36 | Future run() { 37 | // Validate that there's an sdk. 38 | if (!sdkManager.hasSdk) { 39 | sdkManager.showNoSdkMessage(); 40 | return new Future.value(); 41 | } 42 | 43 | // Save any dirty editors. 44 | atom.workspace.getTextEditors().forEach((editor) { 45 | if (editor.isModified()) editor.save(); 46 | }); 47 | 48 | // Build plugins and aggregate the results 49 | var builds = _projectsToBuild().map((String name) => _runBuild(name)); 50 | Future result = Future.wait(builds).then((List results) => 51 | results.reduce((bool value, bool success) => value && success)); 52 | 53 | return result.then((bool success) { 54 | if (success) { 55 | new Future.delayed(new Duration(seconds: 2)).then((_) => atom.reload()); 56 | } 57 | }); 58 | } 59 | 60 | /// Locate and build the specified project. 61 | Future _runBuild(String projName) { 62 | // Find the project to be built. 63 | Directory proj = atom.project.getDirectories().firstWhere( 64 | (d) => d.getBaseName().endsWith(projName), orElse: () => null 65 | ); 66 | if (proj == null) { 67 | atom.notifications.addWarning("Unable to find project '${projName}'."); 68 | return new Future.value(false); 69 | } 70 | 71 | List args = ['grinder', 'build']; 72 | 73 | // Run the build and check for an exit code of `0` from grind build. 74 | return new PubRunJob.local(proj.getPath(), args, title: projName) 75 | .schedule().then((JobStatus status) => status.isOk && status.result == 0); 76 | } 77 | } 78 | 79 | List _projectsToBuild() => 80 | atom.config.getValue('$pluginId.buildAtomPlugins') as List ?? []; 81 | -------------------------------------------------------------------------------- /lib/impl/status_display.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library atom.status; 6 | 7 | import 'dart:async'; 8 | 9 | import 'package:atom/atom.dart'; 10 | import 'package:atom/utils/disposable.dart'; 11 | import 'package:atom/utils/string_utils.dart'; 12 | 13 | import '../atom_statusbar.dart'; 14 | import '../elements.dart'; 15 | import '../jobs.dart'; 16 | import '../state.dart'; 17 | 18 | const Duration _showDelay = const Duration(milliseconds: 200); 19 | 20 | const Duration _hideDelay = const Duration(milliseconds: 400); 21 | 22 | class StatusDisplay implements Disposable { 23 | final Disposables _disposables = new Disposables(); 24 | StreamSubscription _subscription; 25 | 26 | JobsDialog dialog; 27 | 28 | Tile _statusbarTile; 29 | 30 | Timer _showTimer; 31 | Timer _hideTimer; 32 | 33 | StatusDisplay(StatusBar statusBar) { 34 | CoreElement spinner; 35 | CoreElement textLabel; 36 | CoreElement countBadge; 37 | 38 | CoreElement statusElement = div(c: 'job-status-bar dart') 39 | ..inlineBlock() 40 | ..click(_showJobsDialog) 41 | ..add([ 42 | spinner = img() 43 | ..inlineBlockTight() 44 | ..clazz('status-spinner') 45 | ..src = 'atom://dart/images/gear.svg', 46 | textLabel = div(c: 'text-label')..inlineBlockTight(), // text-highlight 47 | countBadge = span(c: 'badge badge-info badge-count') 48 | ]); 49 | 50 | _statusbarTile = 51 | statusBar.addRightTile(item: statusElement.element, priority: 1000); 52 | 53 | _subscription = jobs.onQueueChanged.listen((_) { 54 | Job job = jobs.activeJob; 55 | 56 | bool shouldShow = job != null; 57 | bool isShowing = statusElement.element.classes.contains('showing'); 58 | 59 | if (shouldShow && !isShowing) { 60 | _hideTimer?.cancel(); 61 | _hideTimer = null; 62 | 63 | // Show it. 64 | if (_showTimer == null) { 65 | _showTimer = new Timer(_showDelay, () { 66 | statusElement.toggleClass('showing', true); 67 | _showTimer = null; 68 | }); 69 | } 70 | } else if (!shouldShow && isShowing) { 71 | _showTimer?.cancel(); 72 | _showTimer = null; 73 | 74 | // Hide it. 75 | if (_hideTimer == null) { 76 | _hideTimer = new Timer(_hideDelay, () { 77 | textLabel.text = ''; 78 | statusElement.toggleClass('showing', false); 79 | _hideTimer = null; 80 | }); 81 | } 82 | } 83 | 84 | if (job != null) { 85 | textLabel.text = '${job.name}…'; 86 | } 87 | 88 | int jobsLength = jobs.allJobs.length; 89 | countBadge.text = jobsLength == 0 ? '' : '${jobsLength} ${pluralize('job', jobsLength)}'; 90 | 91 | spinner.toggleClass('showing', shouldShow); 92 | textLabel.toggleClass('showing', shouldShow); 93 | countBadge.toggleClass('showing', jobsLength > 1); 94 | 95 | _updateJobsDialog(); 96 | }); 97 | 98 | _disposables.add(atom.commands.add( 99 | 'atom-workspace', 'dart:show-jobs', (_) => _showJobsDialog())); 100 | } 101 | 102 | void dispose() { 103 | _subscription.cancel(); 104 | _statusbarTile.destroy(); 105 | _disposables.dispose(); 106 | } 107 | 108 | void _showJobsDialog() { 109 | if (dialog == null) { 110 | dialog = new JobsDialog(); 111 | _disposables.add(dialog); 112 | } 113 | dialog.show(); 114 | dialog.updateJobsDialog(); 115 | } 116 | 117 | void _updateJobsDialog() { 118 | if (dialog != null) dialog.updateJobsDialog(); 119 | } 120 | } 121 | 122 | class JobsDialog implements Disposable { 123 | TitledModelDialog dialog; 124 | CoreElement _listGroup; 125 | 126 | JobsDialog() { 127 | dialog = new TitledModelDialog('', classes: 'list-dialog'); 128 | dialog.content.add([ 129 | div(c: 'select-list')..add([_listGroup = ol(c: 'list-group')]) 130 | ]); 131 | } 132 | 133 | void show() => dialog.show(); 134 | 135 | void updateJobsDialog() { 136 | dialog.title.text = jobs.allJobs.isEmpty ? 137 | 'No running jobs.' : 138 | '${jobs.allJobs.length} running ${pluralize('job', jobs.allJobs.length)}'; 139 | _listGroup.element.children.clear(); 140 | 141 | for (JobInstance jobInstance in jobs.allJobs) { 142 | Job job = jobInstance.job; 143 | 144 | CoreElement item = li(c: 'item-container') 145 | ..layoutHorizontal() 146 | ..add([ 147 | div() 148 | ..inlineBlock() 149 | ..flex() 150 | ..text = jobInstance.isRunning ? '${job.name}…' : job.name 151 | ]); 152 | 153 | if (job.infoAction != null) { 154 | item.add([ 155 | div(c: 'info') 156 | ..inlineBlock() 157 | ..icon('question') 158 | ..click(job.infoAction) 159 | ]); 160 | } 161 | 162 | if (jobInstance.isRunning) { 163 | item.add([ 164 | div(c: 'jobs-progress') 165 | ..inlineBlock() 166 | ..add([new ProgressElement()]) 167 | ]); 168 | } 169 | 170 | _listGroup.add(item); 171 | } 172 | } 173 | 174 | void dispose() => dialog.dispose(); 175 | } 176 | -------------------------------------------------------------------------------- /lib/impl/testing_utils.dart: -------------------------------------------------------------------------------- 1 | 2 | /// Return the list of possible test paths. The given [path] must be in the 3 | /// `lib/` directory. 4 | List getPossibleTestPaths(String path, String separator) { 5 | assert(separator.length == 1); 6 | 7 | if (!path.startsWith('lib${separator}')) return []; 8 | path = path.substring(4); 9 | 10 | List result = []; 11 | 12 | // Remove '.dart'; add '_test.dart'. 13 | path = path.substring(0, path.length - 5) + '_test.dart'; 14 | 15 | // Look for test/path/file_test.dart. 16 | String testPath = 'test' + separator + path; 17 | result.add(testPath); 18 | 19 | // Look for test/path-w/o-src/file_test.dart. 20 | if (path.startsWith('src${separator}')) { 21 | path = path.substring(4); 22 | testPath = 'test' + separator + path; 23 | result.add(testPath); 24 | } 25 | 26 | // Look for test/file_test.dart. 27 | if (path.contains('/')) { 28 | testPath = 'test' + separator + _basename(path, separator: separator); 29 | result.add(testPath); 30 | } 31 | 32 | return result; 33 | } 34 | 35 | String _basename(String name, { String separator}) { 36 | int index = name.lastIndexOf(separator); 37 | return index == -1 ? name : name.substring(index + 1); 38 | } 39 | -------------------------------------------------------------------------------- /lib/impl/toolbar.dart: -------------------------------------------------------------------------------- 1 | import 'dart:js'; 2 | 3 | import 'package:atom/atom.dart'; 4 | 5 | /// A wrapper around the `toolbar` API. 6 | class Toolbar extends ProxyHolder { 7 | Toolbar(JsObject obj) : super(obj); 8 | 9 | ToolbarTile addLeftTile({dynamic item, int priority}) { 10 | Map m = {'item': item}; 11 | if (priority != null) m['priority'] = priority; 12 | return new ToolbarTile(invoke('addLeftTile', m)); 13 | } 14 | 15 | ToolbarTile addRightTile({dynamic item, int priority}) { 16 | Map m = {'item': item}; 17 | if (priority != null) m['priority'] = priority; 18 | return new ToolbarTile(invoke('addRightTile', m)); 19 | } 20 | 21 | List getLeftTiles() => 22 | new List.from(invoke('getLeftTiles').map((t) => new ToolbarTile(t))); 23 | 24 | List getRightTiles() => 25 | new List.from(invoke('getRightTiles').map((t) => new ToolbarTile(t))); 26 | } 27 | 28 | class ToolbarTile extends ProxyHolder { 29 | ToolbarTile(JsObject obj) : super(obj); 30 | 31 | int getPriority() => invoke('getPriority'); 32 | dynamic getItem() => invoke('getItem'); 33 | void destroy() => invoke('destroy'); 34 | } 35 | -------------------------------------------------------------------------------- /lib/launch/launch_cli.dart: -------------------------------------------------------------------------------- 1 | library atom.launch_cli; 2 | 3 | import 'dart:async'; 4 | 5 | import 'package:atom/atom.dart'; 6 | import 'package:atom/node/fs.dart'; 7 | import 'package:atom/node/process.dart'; 8 | import 'package:atom_dart/dartino/dartino.dart' show dartino; 9 | 10 | import '../debug/observatory_debugger.dart' show ObservatoryDebugger; 11 | import '../projects.dart'; 12 | import '../sdk.dart'; 13 | import '../state.dart'; 14 | import 'launch.dart'; 15 | 16 | final String _observatoryPrefix = 'Observatory listening on '; 17 | 18 | class CliLaunchType extends LaunchType { 19 | static void register(LaunchManager manager) => 20 | manager.registerLaunchType(new CliLaunchType()); 21 | 22 | CliLaunchType() : super('cli'); 23 | 24 | bool canLaunch(String path, LaunchData data) { 25 | if (!path.endsWith('.dart')) return false; 26 | 27 | DartProject project = projectManager.getProjectFor(path); 28 | 29 | if (project == null) { 30 | return data.hasMain; 31 | } else { 32 | // Check that the file is not in lib/. 33 | String relativePath = fs.relativize(project.path, path); 34 | if (relativePath.startsWith('lib${fs.separator}')) return false; 35 | 36 | if (dartino.isProject(project.directory.path)) return false; 37 | return data.hasMain; 38 | } 39 | } 40 | 41 | Future performLaunch(LaunchManager manager, LaunchConfiguration configuration) { 42 | Sdk sdk = sdkManager.sdk; 43 | 44 | if (sdk == null) new Future.error('No Dart SDK configured'); 45 | 46 | bool withDebug = configuration.debug; 47 | String path = configuration.primaryResource; 48 | String cwd = configuration.cwd; 49 | List args = configuration.argsAsList; 50 | 51 | DartProject project = projectManager.getProjectFor(path); 52 | 53 | // Determine the best cwd. 54 | if (cwd == null) { 55 | if (project == null) { 56 | List paths = atom.project.relativizePath(path); 57 | if (paths[0] != null) { 58 | cwd = paths[0]; 59 | path = paths[1]; 60 | } 61 | } else { 62 | cwd = project.path; 63 | path = fs.relativize(cwd, path); 64 | } 65 | } else { 66 | path = fs.relativize(cwd, path); 67 | } 68 | 69 | List _args = []; 70 | 71 | int observatoryPort; 72 | 73 | if (withDebug) { 74 | observatoryPort = getOpenPort(); 75 | _args.add('--enable-vm-service:${observatoryPort}'); 76 | _args.add('--pause_isolates_on_start=true'); 77 | } 78 | 79 | if (configuration.checked) _args.add('--checked'); 80 | 81 | _args.add(path); 82 | if (args != null) _args.addAll(args); 83 | 84 | String description = (args == null || args.isEmpty) ? path : '${path} ${args.join(' ')}'; 85 | 86 | // Run in `underShell` to capture environment variables on the mac. 87 | ProcessRunner runner = new ProcessRunner.underShell( 88 | sdk.dartVm.path, 89 | args: _args, 90 | cwd: cwd 91 | ); 92 | 93 | Launch launch = new _CliLaunch(manager, this, configuration, path, 94 | killHandler: () => runner.kill(), 95 | cwd: cwd, 96 | project: project, 97 | title: description 98 | ); 99 | manager.addLaunch(launch); 100 | 101 | runner.execStreaming(); 102 | runner.onStdout.listen((str) { 103 | // "Observatory listening on http://127.0.0.1:xxx\n" 104 | if (str.startsWith(_observatoryPrefix)) { 105 | // str is 'http://127.0.0.1:xxx'. 106 | str = str.substring(_observatoryPrefix.length).trim(); 107 | 108 | launch.servicePort.value = observatoryPort; 109 | 110 | ObservatoryDebugger.connect(launch, 'localhost', observatoryPort).catchError((e) { 111 | launch.pipeStdio( 112 | 'Unable to connect to the observatory (port ${observatoryPort}).\n', 113 | error: true 114 | ); 115 | }); 116 | } else { 117 | launch.pipeStdio(str); 118 | } 119 | }); 120 | runner.onStderr.listen((str) => launch.pipeStdio(str, error: true)); 121 | runner.onExit.then((int code) => launch.launchTerminated(code)); 122 | 123 | return new Future.value(launch); 124 | } 125 | 126 | String getDefaultConfigText() { 127 | return ''' 128 | # Additional args for the application. 129 | args: 130 | # The working directory to use for the launch. 131 | cwd: 132 | # Enable or disable checked mode. 133 | checked: true 134 | # Enable or disable debugging. 135 | debug: true 136 | '''; 137 | } 138 | } 139 | 140 | // TODO: Move more launching functionality into this class. 141 | class _CliLaunch extends Launch { 142 | CachingServerResolver _resolver; 143 | 144 | _CliLaunch( 145 | LaunchManager manager, 146 | LaunchType launchType, 147 | LaunchConfiguration launchConfiguration, 148 | String name, 149 | { Function killHandler, String cwd, DartProject project, String title } 150 | ) : super( 151 | manager, 152 | launchType, 153 | launchConfiguration, 154 | name, 155 | killHandler: killHandler, 156 | cwd: cwd, 157 | title: title 158 | ) { 159 | _resolver = new CachingServerResolver( 160 | cwd: project?.path, 161 | server: analysisServer 162 | ); 163 | 164 | exitCode.onChanged.first.then((_) => _resolver.dispose()); 165 | } 166 | 167 | Future resolve(String url) => _resolver.resolve(url); 168 | } 169 | -------------------------------------------------------------------------------- /lib/launch/launch_serve.dart: -------------------------------------------------------------------------------- 1 | library atom.launch_serve; 2 | 3 | import 'dart:async'; 4 | 5 | import 'package:atom/atom.dart'; 6 | import 'package:atom/node/fs.dart'; 7 | import 'package:atom/node/process.dart'; 8 | 9 | import '../projects.dart'; 10 | import '../state.dart'; 11 | import 'launch.dart'; 12 | 13 | const singletonBoolParameters = const ['all', 'force-poll', 'no-force-poll']; 14 | 15 | /// Pub serve launch. 16 | class ServeLaunchType extends LaunchType { 17 | static void register(LaunchManager manager) => 18 | manager.registerLaunchType(new ServeLaunchType()); 19 | 20 | ServeLaunchType() : super('serve'); 21 | 22 | bool canLaunch(String path, LaunchData data) { 23 | return path.endsWith('pubspec.yaml'); 24 | } 25 | 26 | bool get supportsChecked => false; 27 | 28 | Future performLaunch( 29 | LaunchManager manager, LaunchConfiguration configuration) { 30 | String cwd = configuration.cwd; 31 | var args = configuration.typeArgs['args'] ?? {}; 32 | 33 | String launchName = 'pub serve'; 34 | 35 | // Determine the best cwd. 36 | if (cwd == null) { 37 | List paths = 38 | atom.project.relativizePath(configuration.primaryResource); 39 | if (paths[0] != null) { 40 | cwd = paths[0]; 41 | launchName = paths[1]; 42 | } 43 | } else { 44 | launchName = fs.relativize(cwd, launchName); 45 | } 46 | 47 | List execArgs = ['serve']; 48 | if (args is Map) { 49 | args.forEach((k, v) { 50 | if (singletonBoolParameters.contains(k)) { 51 | if (v) execArgs.add('--$k'); 52 | } else { 53 | execArgs.add('--$k=$v'); 54 | } 55 | }); 56 | } 57 | ProcessRunner runner = 58 | sdkManager.sdk.execBin('pub', execArgs, cwd: cwd, startProcess: false); 59 | 60 | String root = 61 | 'http://${args['hostname'] ?? 'localhost'}:${args['port'] ?? 'port'}'; 62 | Launch launch = new ServeLaunch( 63 | manager, this, configuration, launchName, root, 64 | killHandler: () => runner.kill(), cwd: cwd, title: launchName); 65 | manager.addLaunch(launch); 66 | 67 | runner.execStreaming(); 68 | runner.onStdout.listen((str) => launch.pipeStdio(str)); 69 | runner.onStderr.listen((str) => launch.pipeStdio(str, error: true)); 70 | runner.onExit.then((code) => launch.launchTerminated(code)); 71 | 72 | return new Future.value(launch); 73 | } 74 | 75 | String getDefaultConfigText() { 76 | return ''' 77 | # Additional args for pub serve 78 | args: 79 | # Mode to run transformers in. (defaults to "debug") 80 | #mode: debug 81 | # Use all default source directories. 82 | #all: true 83 | # The JavaScript compiler to use to build the app. [dart2js, dartdevc, none] 84 | #web-compiler: dartdevc 85 | # Defines an environment constant for dart2js. 86 | #define: variable=value[,variable=value] 87 | # The hostname to listen on. (defaults to "localhost") 88 | #hostname: localhost 89 | # The base port to listen on. (defaults to "8084") 90 | port: 8084 91 | # Force the use of a polling filesystem watcher. 92 | #force-poll: true 93 | #no-force-poll: true 94 | '''; 95 | } 96 | } 97 | 98 | class ServeLaunch extends Launch { 99 | String _root; 100 | String get root => _root; 101 | 102 | ServeLaunch(LaunchManager manager, LaunchType launchType, 103 | LaunchConfiguration launchConfiguration, String name, String root, 104 | {Function killHandler, String cwd, DartProject project, String title}) 105 | : _root = root, 106 | super(manager, launchType, launchConfiguration, name, 107 | killHandler: killHandler, cwd: cwd, title: title); 108 | } 109 | -------------------------------------------------------------------------------- /lib/launch/launch_shell.dart: -------------------------------------------------------------------------------- 1 | library atom.launch_shell; 2 | 3 | import 'dart:async'; 4 | 5 | import 'package:atom/atom.dart'; 6 | import 'package:atom/node/fs.dart'; 7 | import 'package:atom/node/process.dart'; 8 | 9 | import 'launch.dart'; 10 | 11 | class ShellLaunchType extends LaunchType { 12 | static void register(LaunchManager manager) => 13 | manager.registerLaunchType(new ShellLaunchType()); 14 | 15 | ShellLaunchType() : super('shell'); 16 | 17 | bool canLaunch(String path, LaunchData data) { 18 | return path.endsWith('.sh') || path.endsWith('.bat'); 19 | } 20 | 21 | bool get supportsChecked => false; 22 | 23 | Future performLaunch(LaunchManager manager, LaunchConfiguration configuration) { 24 | String script = configuration.primaryResource; 25 | String cwd = configuration.cwd; 26 | List args = configuration.argsAsList; 27 | 28 | String launchName = script; 29 | 30 | // Determine the best cwd. 31 | if (cwd == null) { 32 | List paths = atom.project.relativizePath(script); 33 | if (paths[0] != null) { 34 | cwd = paths[0]; 35 | launchName = paths[1]; 36 | } 37 | } else { 38 | launchName = fs.relativize(cwd, launchName); 39 | } 40 | 41 | ProcessRunner runner = new ProcessRunner(script, args: args, cwd: cwd); 42 | String description = (args == null || args.isEmpty) ? launchName : '${launchName} ${args.join(' ')}'; 43 | Launch launch = new Launch(manager, this, configuration, launchName, 44 | killHandler: () => runner.kill(), 45 | cwd: cwd, 46 | title: description 47 | ); 48 | manager.addLaunch(launch); 49 | 50 | runner.execStreaming(); 51 | runner.onStdout.listen((str) => launch.pipeStdio(str)); 52 | runner.onStderr.listen((str) => launch.pipeStdio(str, error: true)); 53 | runner.onExit.then((code) => launch.launchTerminated(code)); 54 | 55 | return new Future.value(launch); 56 | } 57 | 58 | String getDefaultConfigText() { 59 | return ''' 60 | # Additional args for the application. 61 | args: 62 | '''; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /lib/launch/launch_web.dart: -------------------------------------------------------------------------------- 1 | library atom.launch_web; 2 | 3 | import 'dart:async'; 4 | 5 | import 'package:atom/atom.dart'; 6 | import 'package:atom/node/fs.dart'; 7 | import 'package:atom/node/process.dart'; 8 | 9 | import '../browser.dart'; 10 | import '../debug/chrome_debugger.dart'; 11 | import '../state.dart'; 12 | import 'launch.dart'; 13 | import 'launch_serve.dart'; 14 | 15 | const launchOptionKeys = const [ 16 | 'debugging', 17 | 'local_pub_serve', 18 | 'pub_serve_host' 19 | ]; 20 | 21 | class WebLaunchType extends LaunchType { 22 | static void register(LaunchManager manager) => 23 | manager.registerLaunchType(new WebLaunchType()); 24 | 25 | WebLaunchType() : super('web'); 26 | 27 | bool canLaunch(String path, LaunchData data) => path.endsWith('.html'); 28 | 29 | Future performLaunch( 30 | LaunchManager manager, LaunchConfiguration configuration) { 31 | Browser browser = deps[BrowserManager].browser; 32 | if (browser == null) { 33 | atom.notifications.addWarning('No browser configured.'); 34 | return new Future.value(); 35 | } 36 | 37 | Map yamlArgs = configuration.typeArgs['args'] ?? {}; 38 | bool debugging = yamlArgs['debugging'] == true; 39 | bool pub_serve_check = yamlArgs['local_pub_serve'] == true; 40 | 41 | String root; 42 | if (pub_serve_check) { 43 | // Find pub serve for 'me'. 44 | ServeLaunch pubServe = manager.launches.firstWhere( 45 | (l) => 46 | l is ServeLaunch && 47 | l.isRunning && 48 | l.launchConfiguration.projectPath == configuration.projectPath, 49 | orElse: () => null); 50 | 51 | if (pubServe == null) { 52 | atom.notifications.addWarning('No pub serve launch found.'); 53 | return new Future.value(); 54 | } 55 | root = pubServe.root; 56 | } else { 57 | root = yamlArgs['pub_serve_host'] ?? 'http://localhost:8084'; 58 | } 59 | 60 | List args = 61 | browser.execArgsFromYaml(yamlArgs, exceptKeys: launchOptionKeys); 62 | String htmlFile = configuration.shortResourceName; 63 | if (htmlFile.startsWith('web/') || htmlFile.startsWith('web\\')) { 64 | htmlFile = htmlFile.substring(4); 65 | } 66 | 67 | if (!debugging) { 68 | args.add('$root/$htmlFile'); 69 | } 70 | 71 | print(browser.path); 72 | print(args); 73 | 74 | ProcessRunner runner = 75 | new ProcessRunner.underShell(browser.path, args: args); 76 | 77 | Launch launch = new Launch( 78 | manager, this, configuration, configuration.shortResourceName, 79 | killHandler: () => runner.kill(), 80 | title: configuration.shortResourceName); 81 | manager.addLaunch(launch); 82 | 83 | runner.execStreaming(); 84 | runner.onStdout.listen((str) => launch.pipeStdio(str)); 85 | runner.onStderr.listen((str) => launch.pipeStdio(str, error: true)); 86 | runner.onExit.then((code) => launch.launchTerminated(code)); 87 | 88 | if (debugging) { 89 | String debugHost = 'localhost:${yamlArgs['remote-debugging-port']}'; 90 | ChromeDebugger 91 | .connect(launch, configuration, debugHost, root, htmlFile) 92 | .catchError((e) { 93 | launch.pipeStdio('Unable to connect to chrome.\n', error: true); 94 | }); 95 | } 96 | 97 | return new Future.value(launch); 98 | } 99 | 100 | String getDefaultConfigText() => ''' 101 | # Additional args for browser. 102 | args: 103 | # options 104 | debugging: true 105 | local_pub_serve: true 106 | # if local_pub_serve is false, specify pub serve endpoint 107 | pub_serve_host: http://localhost:8084 108 | 109 | # chrome 110 | remote-debugging-port: 9222 111 | user-data-dir: ${fs.tmpdir}/dart-dbg-host 112 | no-default-browser-check: true 113 | no-first-run: true 114 | '''; 115 | } 116 | -------------------------------------------------------------------------------- /lib/state.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library atom.state; 6 | 7 | import 'dart:async'; 8 | import 'dart:convert' show JSON; 9 | 10 | import 'package:atom/utils/dependencies.dart'; 11 | 12 | import 'analysis_server.dart'; 13 | import 'debug/breakpoints.dart'; 14 | import 'debug/debugger.dart'; 15 | import 'debug/debugger_tooltip.dart'; 16 | import 'editors.dart'; 17 | import 'error_repository.dart'; 18 | import 'impl/navigation.dart'; 19 | import 'impl/status.dart'; 20 | import 'jobs.dart'; 21 | import 'launch/launch.dart'; 22 | import 'launch/launch_configs.dart'; 23 | import 'projects.dart'; 24 | import 'sdk.dart'; 25 | 26 | export 'package:atom/utils/dependencies.dart' show deps; 27 | 28 | final String pluginId = 'dart'; 29 | 30 | AtomAnalysisServer get analysisServer => deps[AtomAnalysisServer]; 31 | EditorManager get editorManager => deps[EditorManager]; 32 | ErrorRepository get errorRepository => deps[ErrorRepository]; 33 | JobManager get jobs => deps[JobManager]; 34 | LaunchManager get launchManager => deps[LaunchManager]; 35 | LaunchConfigurationManager get launchConfigurationManager => deps[LaunchConfigurationManager]; 36 | DebugManager get debugManager => deps[DebugManager]; 37 | DebugTooltipManager get debugTooltipManager => deps[DebugTooltipManager]; 38 | BreakpointManager get breakpointManager => deps[BreakpointManager]; 39 | ProjectManager get projectManager => deps[ProjectManager]; 40 | SdkManager get sdkManager => deps[SdkManager]; 41 | NavigationManager get navigationManager => deps[NavigationManager]; 42 | StatusViewManager get statusViewManager => deps[StatusViewManager]; 43 | 44 | final State state = new State(); 45 | 46 | class State { 47 | dynamic _pluginState = {}; 48 | Map _storables = {}; 49 | 50 | Map _controllers = {}; 51 | 52 | State(); 53 | 54 | dynamic operator[](String key) => _pluginState[key]; 55 | 56 | void operator[]=(String key, dynamic value) { 57 | _pluginState[key] = value; 58 | if (_controllers[key] != null) _controllers[key].add(value); 59 | } 60 | 61 | /// Register the given [StateStorable]. This will call [StateStorable.fromStored] 62 | /// before it returns. 63 | void registerStorable(String key, StateStorable storable) { 64 | try { 65 | _storables[key] = storable; 66 | var data = this[key]; 67 | storable.initFromStored(data is String ? JSON.decode(data) : null); 68 | } catch (e) { 69 | print('Exception restoring state: ${e}'); 70 | } 71 | } 72 | 73 | void loadFrom(dynamic inState) { 74 | _pluginState = inState ?? {}; 75 | } 76 | 77 | Stream onValueChanged(String key) { 78 | if (_controllers[key] != null) { 79 | return _controllers[key].stream; 80 | } else { 81 | StreamController controller = new StreamController.broadcast( 82 | sync: true, 83 | onCancel: () => _controllers.remove(key)); 84 | _controllers[key] = controller; 85 | return controller.stream; 86 | } 87 | } 88 | 89 | dynamic saveState() { 90 | _storables.forEach((String key, StateStorable storable) { 91 | _pluginState[key] = JSON.encode(storable.toStorable()); 92 | }); 93 | return _pluginState; 94 | } 95 | } 96 | 97 | abstract class StateStorable { 98 | StateStorable(); 99 | 100 | /// Initialize the state from a previously stored JSON encodable value. 101 | void initFromStored(dynamic storedData); 102 | 103 | /// Write the current state to a JSON encodable value. 104 | dynamic toStorable(); 105 | } 106 | -------------------------------------------------------------------------------- /lib/usage.dart: -------------------------------------------------------------------------------- 1 | library atom.usage; 2 | 3 | import 'dart:async'; 4 | 5 | import 'package:atom/atom.dart'; 6 | import 'package:atom/node/package.dart'; 7 | import 'package:atom/node/workspace.dart'; 8 | import 'package:atom/utils/disposable.dart'; 9 | import 'package:logging/logging.dart'; 10 | import 'package:usage/usage_html.dart'; 11 | 12 | import 'projects.dart'; 13 | import 'state.dart'; 14 | 15 | // TODO: If re-enabling analytics, replace with a real UA ID. 16 | final String _UA = 'UA-0000'; 17 | 18 | // TODO: Remove this constant if re-enabling analytics. Also, see plugin.dart, 19 | // 'sendUsage'. 20 | final bool kAnalyticsEnabled = false; 21 | 22 | Analytics _ga = new AnalyticsMock(); 23 | 24 | class UsageManager implements Disposable { 25 | StreamSubscriptions _subs = new StreamSubscriptions(); 26 | Disposable _editorObserve; 27 | 28 | UsageManager() { 29 | _init().then((_) => trackCommand('auto-startup')); 30 | } 31 | 32 | Future _init() { 33 | return atomPackage.getPackageVersion().then((String version) { 34 | atom.config.observe('${pluginId}.sendUsage', null, (value) { 35 | // Disable Google Analytics if the UA is the placeholder one. 36 | if (_UA.startsWith('UA-0000')) value = false; 37 | 38 | if (kAnalyticsEnabled && value == true) { 39 | _ga = new AnalyticsHtml(_UA, pluginId, version); 40 | _ga.optIn = true; 41 | _ga.sendScreenView('editor'); 42 | } else { 43 | _ga = new AnalyticsMock(); 44 | } 45 | }); 46 | 47 | _subs.add(Logger.root.onRecord.listen(_handleLogRecord)); 48 | _subs.add(atom.commands.onDidDispatch.listen(trackCommand)); 49 | 50 | _editorObserve = atom.workspace.observeActivePaneItem(_activePaneItemChanged); 51 | 52 | analysisServer.onActive.listen((val) { 53 | trackCommand(val ? 'auto-analysis-server-start' : 'auto-analysis-server-stop'); 54 | }); 55 | }); 56 | } 57 | 58 | void dispose() { 59 | trackCommand('auto-shutdown'); 60 | _subs.cancel(); 61 | if (_editorObserve != null) _editorObserve.dispose(); 62 | } 63 | } 64 | 65 | void trackCommand(String command) { 66 | String category = 'dart'; 67 | 68 | List list = command.split(':'); 69 | if (list.length >= 2) { 70 | category = list[0]; 71 | command = list[1]; 72 | } 73 | 74 | // Ignore `core:` commands (core:confirm, core:cancel, ...). 75 | if (category == 'core') return; 76 | 77 | // Ignore `dart:newline`. 78 | if (command == 'newline') return; 79 | 80 | _ga.sendEvent(category, command); 81 | } 82 | 83 | void _activePaneItemChanged(_) { 84 | TextEditor editor = atom.workspace.getActiveTextEditor(); 85 | if (editor == null || editor.getPath() == null) return; 86 | 87 | String path = editor.getPath(); 88 | 89 | // Ensure that the file is Dart related. 90 | if (isDartFile(path) || projectManager.getProjectFor(path) != null) { 91 | int index = path.lastIndexOf('.'); 92 | if (index == -1) { 93 | _ga.sendScreenView('editor'); 94 | } else { 95 | String extension = path.substring(index + 1); 96 | _ga.sendScreenView('editor/${extension.toLowerCase()}'); 97 | } 98 | } 99 | } 100 | 101 | void _handleLogRecord(LogRecord log) { 102 | if (log.level >= Level.WARNING) { 103 | bool fatal = log.level >= Level.SEVERE; 104 | String message = log.message; 105 | if (message.contains('/Users/')) { 106 | message = message.substring(0, message.indexOf('/Users/')); 107 | } 108 | String desc = '${log.loggerName}:${message}'; 109 | if (log.error != null) desc += ',${log.error.runtimeType}'; 110 | if (log.stackTrace != null) desc += ',${sanitizeStacktrace(log.stackTrace)}'; 111 | _ga.sendException(desc, fatal: fatal); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /lib/utils.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library atom.utils; 6 | 7 | import 'dart:async'; 8 | 9 | final String loremIpsum = "Lorem ipsum dolor sit amet, consectetur adipiscing " 10 | "elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. " 11 | "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi " 12 | "ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit" 13 | " in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur" 14 | " sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt " 15 | "mollit anim id est laborum."; 16 | 17 | /// A value that fires events when it changes. 18 | class Property { 19 | T _value; 20 | StreamController _controller = new StreamController.broadcast(); 21 | 22 | Property([T initialValue]) { 23 | _value = initialValue; 24 | } 25 | 26 | T get value => _value; 27 | set value(T v) { 28 | if (_value != v) { 29 | _value = v; 30 | _controller.add(_value); 31 | } 32 | } 33 | 34 | bool get hasValue => _value != null; 35 | 36 | Stream get onChanged => _controller.stream; 37 | 38 | StreamSubscription observe(callback(T t)) { 39 | callback(value); 40 | return onChanged.listen(callback); 41 | } 42 | 43 | String toString() => '${_value}'; 44 | } 45 | 46 | /// A SelectionGroup: 47 | /// - manages a set of items 48 | /// - fires notifications when the set changes 49 | /// - has a notion of a 'selected' or active item 50 | class SelectionGroup { 51 | T _selection; 52 | List _items = []; 53 | 54 | StreamController _addedController = new StreamController.broadcast(); 55 | StreamController _selectionChangedController = new StreamController.broadcast(); 56 | StreamController _removedController = new StreamController.broadcast(); 57 | StreamController _mutationController = new StreamController.broadcast(); 58 | 59 | SelectionGroup(); 60 | 61 | T get selection => _selection; 62 | 63 | List get items => _items; 64 | 65 | bool get isEmpty => _items.isEmpty; 66 | bool get isNotEmpty => _items.isNotEmpty; 67 | int get length => _items.length; 68 | 69 | Stream get onAdded => _addedController.stream; 70 | Stream get onSelectionChanged => _selectionChangedController.stream; 71 | Stream get onRemoved => _removedController.stream; 72 | 73 | StreamSubscription> observeMutation(callback(List list)) { 74 | callback(items); 75 | return _mutationController.stream.map((_) => items).listen(callback); 76 | } 77 | 78 | void add(T item) { 79 | _items.add(item); 80 | _addedController.add(item); 81 | _mutationController.add(item); 82 | 83 | if (_selection == null) { 84 | _selection = item; 85 | _selectionChangedController.add(selection); 86 | } 87 | } 88 | 89 | void setSelection(T sel) { 90 | if (_selection != sel) { 91 | _selection = sel; 92 | _selectionChangedController.add(selection); 93 | } 94 | } 95 | 96 | void remove(T item) { 97 | if (!_items.contains(item)) return; 98 | 99 | _items.remove(item); 100 | _removedController.add(item); 101 | _mutationController.add(item); 102 | 103 | if (_selection == item) { 104 | _selection = null; 105 | _selectionChangedController.add(null); 106 | } 107 | } 108 | } 109 | 110 | typedef Future ReturnsFuture(); 111 | 112 | class FutureSerializer { 113 | List _operations = []; 114 | List> _completers = []; 115 | 116 | Future perform(ReturnsFuture operation) { 117 | Completer completer = new Completer(); 118 | 119 | _operations.add(operation); 120 | _completers.add(completer); 121 | 122 | if (_operations.length == 1) { 123 | _serviceQueue(); 124 | } 125 | 126 | return completer.future; 127 | } 128 | 129 | void _serviceQueue() { 130 | ReturnsFuture operation = _operations.first; 131 | Completer completer = _completers.first; 132 | 133 | Future future = operation(); 134 | future.then((value) { 135 | completer.complete(value); 136 | }).catchError((e) { 137 | completer.completeError(e); 138 | }).whenComplete(() { 139 | _operations.removeAt(0); 140 | _completers.removeAt(0); 141 | 142 | if (_operations.isNotEmpty) _serviceQueue(); 143 | }); 144 | } 145 | } 146 | 147 | abstract class MItem { 148 | String get id; 149 | String key; 150 | } 151 | 152 | class Pair { 153 | final L left; 154 | final R right; 155 | Pair(this.left, this.right); 156 | } 157 | 158 | bool listIdentical(List a, List b) { 159 | if (a.length != b.length) return false; 160 | 161 | for (int i = 0; i < a.length; i++) { 162 | var _a = a[i]; 163 | var _b = b[i]; 164 | if (_a == null && _b != null) return false; 165 | if (_a != null && _b == null) return false; 166 | if (_a != _b) return false; 167 | } 168 | 169 | return true; 170 | } 171 | -------------------------------------------------------------------------------- /menus/atom-dart.cson: -------------------------------------------------------------------------------- 1 | 'menu': [ 2 | { 3 | label: 'Packages' 4 | submenu: [ 5 | { 6 | label: 'Dart' 7 | submenu: [ 8 | { label: 'Package Settings…', command: 'dart:settings' } 9 | { type: 'separator' } 10 | { label: 'Re-analyze Sources', command: 'dart:reanalyze-sources' } 11 | { label: 'Analysis Server Status', command: 'dart:analysis-server-status' } 12 | { type: 'separator' } 13 | { label: 'Getting Started…', command: 'dart:getting-started' } 14 | { label: 'Release Notes', command: 'dart:release-notes' } 15 | { label: 'Send Feedback…', command: 'dart:send-feedback'} 16 | ] 17 | } 18 | ] 19 | } 20 | { 21 | label: 'View' 22 | submenu: [ 23 | { label: 'Toggle Outline View', command: 'dart:toggle-outline-view'} 24 | ] 25 | } 26 | ] 27 | 28 | 'context-menu': 29 | 'atom-pane[data-active-item-path$="pubspec.yaml"] atom-text-editor[data-grammar~="yaml"]': [ 30 | { label: 'Pub Get', command: 'dart:pub-get'} 31 | { label: 'Pub Upgrade', command: 'dart:pub-upgrade'} 32 | { label: 'Pub Serve', command: 'dart:pub-serve'} 33 | { type: 'separator'} 34 | ] 35 | 'atom-pane[data-active-item-path$=".dart"] atom-text-editor[data-grammar~="dart"]': [ 36 | { label: 'Find References…', command: 'dart:find-references' } 37 | { label: 'Format', command: 'dart:dart-format' } 38 | { label: 'Organize Directives', command: 'dart:organize-directives' } 39 | # TODO(danrubel) should rename be moved into the Refactor submenu ? 40 | { label: 'Rename…', command: 'dart:refactor-rename' } 41 | { label: 'Refactor', submenu: [ 42 | { label: 'Extract Local…', command: 'dart:refactor-extract-local' } 43 | { label: 'Inline Local…', command: 'dart:refactor-inline-local' } 44 | ]} 45 | { label: 'Sort Members', command: 'dart:sort-members' } 46 | { label: 'Type Hierarchy', command: 'dart:type-hierarchy' } 47 | { label: 'Toggle Breakpoint', command: 'dart:debug-toggle-breakpoint' } 48 | { type: 'separator'} 49 | ] 50 | '.tree-view.full-menu li[is="tree-view-file"] span[data-name$=".dart"]': [ 51 | { label: 'Format', command: 'dart:dart-format' } 52 | { type: 'separator'} 53 | ] 54 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dart", 3 | "main": "./web/entry.dart.js", 4 | "version": "1.0.1", 5 | "description": "A Dart plugin for Atom.", 6 | "keywords": [ 7 | "dart", 8 | "dartlang" 9 | ], 10 | "repository": "https://github.com/dart-atom/dart", 11 | "license": "BSD", 12 | "engines": { 13 | "atom": ">=1.0.0 <2.0.0" 14 | }, 15 | "dependencies": { 16 | "chrome-remote-interface": "0.24.3", 17 | "ws": "1.1.1" 18 | }, 19 | "required-packages": [ 20 | "atom-toolbar", 21 | "linter" 22 | ], 23 | "consumedServices": { 24 | "atom-toolbar": { 25 | "versions": { 26 | "^1.0.0": "consumeToolbar" 27 | } 28 | }, 29 | "linter-indie": { 30 | "versions": { 31 | "2.0.0": "consumeLinter" 32 | } 33 | }, 34 | "status-bar": { 35 | "versions": { 36 | "^1.0.0": "consumeStatusBar" 37 | } 38 | } 39 | }, 40 | "providedServices": { 41 | "autocomplete.provider": { 42 | "versions": { 43 | "2.0.0": "provideAutocomplete" 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: atom_dart 2 | version: 1.0.1 3 | description: A Dart plugin for Atom. 4 | homepage: https://github.com/dart-atom/dart 5 | 6 | environment: 7 | sdk: '>=1.9.0 <2.0.0' 8 | 9 | dependencies: 10 | analysis_server_lib: ^0.1.2+2 11 | atom: 12 | git: https://github.com/dart-atom/atom.dart.git 13 | # path: ../atom.dart 14 | haikunator: ^0.1.0 15 | logging: ^0.11.0 16 | markdown: ^0.11.0 17 | pub_semver: ^1.2.1 18 | usage: ^1.1.0 19 | vm_service_lib: ^0.3.0 20 | yaml: ^2.0.0 21 | source_maps: "^0.10.4" 22 | petitparser: "^1.6.1" 23 | 24 | dev_dependencies: 25 | grinder: ^0.8.0+1 26 | html: ^0.12.0 27 | test: ^0.12.0 28 | which: ^0.1.0 29 | -------------------------------------------------------------------------------- /settings/dart.cson: -------------------------------------------------------------------------------- 1 | '.source.dart': 2 | 'editor': 3 | 'commentStart': '// ' 4 | 'increaseIndentPattern': '(?x) 5 | \\{ [^}"\']* $ 6 | | \\[ [^\\]"\']* $ 7 | | \\( [^)"\']* $ 8 | ' 9 | 'decreaseIndentPattern': '(?x) 10 | ^ \\s* (\\s* /[*] .* [*]/ \\s*)* [}\\])] 11 | ' 12 | -------------------------------------------------------------------------------- /settings/yaml-ext.cson: -------------------------------------------------------------------------------- 1 | '.source.yaml-ext': 2 | 'editor': 3 | 'autoIndentOnPaste': false 4 | 'commentStart': '# ' 5 | 'foldEndPattern': '^\\s*$|^\\s*\\}|^\\s*\\]|^\\s*\\)' 6 | 'increaseIndentPattern': '^\\s*.*(:|-) ?(&\\w+)?(\\{[^}"\']*|\\([^)"\']*)?$' 7 | 'decreaseIndentPattern': '^\\s+\\}$' 8 | -------------------------------------------------------------------------------- /snippets/dart.cson: -------------------------------------------------------------------------------- 1 | '.source.dart': 2 | 'main': 3 | 'prefix': 'main' 4 | 'body': """ 5 | main(List args) { 6 | $1 7 | } 8 | 9 | """ 10 | 'try': 11 | 'prefix': 'try' 12 | 'body': """ 13 | try { 14 | $2 15 | } catch (${1:e}) { 16 | 17 | } 18 | 19 | """ 20 | 'if': 21 | 'prefix': 'if' 22 | 'body': """ 23 | if ($1) { 24 | 25 | } 26 | 27 | """ 28 | 'if Else': 29 | 'prefix': 'ife' 30 | 'body': """ 31 | if ($1) { 32 | 33 | } else { 34 | 35 | } 36 | """ 37 | 'assert': 38 | 'prefix': 'assert' 39 | 'body': """ 40 | assert($1); 41 | 42 | """ 43 | 'print': 44 | 'prefix': 'print' 45 | 'body': """ 46 | print($1); 47 | """ 48 | 'for': 49 | 'prefix': 'for' 50 | 'body': """ 51 | for (var i = 0; i < count; i++) { 52 | 53 | } 54 | """ 55 | 'while': 56 | 'prefix': 'while' 57 | 'body': """ 58 | while ($1) { 59 | 60 | } 61 | """ 62 | 'void': 63 | 'prefix': 'void' 64 | 'body': """ 65 | void ${1:name} () { 66 | 67 | } 68 | """ 69 | 'import': 70 | 'prefix': 'import' 71 | 'body': """ 72 | import '$1'; 73 | """ 74 | 'class': 75 | 'prefix': 'class' 76 | 'body': """ 77 | class ${1:name} { 78 | 79 | } 80 | """ 81 | 'typedef': 82 | 'prefix': 'typedef' 83 | 'body': """ 84 | typedef ${1:type} ${2:name}; 85 | """ 86 | -------------------------------------------------------------------------------- /spec/_spec/animals_test.dart: -------------------------------------------------------------------------------- 1 | import 'test.dart'; 2 | 3 | register() { 4 | registerSuites([ 5 | new CatTest(), 6 | new DogTest() 7 | ]); 8 | } 9 | 10 | class CatTest extends TestSuite { 11 | setUp() { 12 | print('I was set up!'); 13 | } 14 | 15 | tearDown() { 16 | print('I was torn down.'); 17 | } 18 | 19 | Map getTests() => { 20 | 'hasPaws': _hasPaws, 21 | 'has4Paws': _has4Paws 22 | }; 23 | 24 | _hasPaws() { 25 | expect(true, true); 26 | } 27 | 28 | _has4Paws() { 29 | expect(2, 4); 30 | } 31 | } 32 | 33 | class DogTest extends TestSuite { 34 | Map getTests() => { 35 | 'fooBar': _fooBar 36 | }; 37 | 38 | _fooBar() { 39 | expect(4, 4); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /spec/_spec/jasmine.dart: -------------------------------------------------------------------------------- 1 | library jasmine; 2 | 3 | //import 'dart:async'; 4 | import 'dart:js'; 5 | 6 | // describe("A suite", function(done) { 7 | // it("contains spec with an expectation", function() { 8 | // expect(true).toBe(true); 9 | // }); 10 | // }); 11 | 12 | final JsFunction _describe = context['describe']; 13 | final JsFunction _it = context['it']; 14 | final JsFunction _expect = context['expect']; 15 | 16 | typedef dynamic Callback(); 17 | 18 | describe(String name, describeClosure) { 19 | return _describe.apply([name, describeClosure]); 20 | } 21 | 22 | it(String description, Callback test) { 23 | return _it.apply([description, test]); 24 | } 25 | 26 | Expectation expect(dynamic value) { 27 | return new Expectation(_expect.apply([value])); 28 | } 29 | 30 | beforeAll(Callback callback) { 31 | context.callMethod('beforeAll', [callback]); 32 | } 33 | 34 | // TODO: Re-do this to support Jasmine 1.3 35 | // (http://jasmine.github.io/1.3/introduction.html) - runs(), waitsFor(), runs() 36 | beforeEach(Callback callback) { 37 | context.callMethod('beforeEach', [callback]); 38 | // _beforeEach.apply([(_done) { 39 | // Done done = new Done(_done); 40 | // dynamic result = callback(); 41 | // if (result is Future) { 42 | // result 43 | // .then((_) => done.finished()) 44 | // .catchError((e) => done.fail(e)); 45 | // } else { 46 | // done.finished(); 47 | // } 48 | // }]); 49 | } 50 | 51 | afterEach(Callback callback) { 52 | context.callMethod('afterEach', [callback]); 53 | // _afterEach.apply([(_done) { 54 | // Done done = new Done(_done); 55 | // dynamic result = callback(); 56 | // if (result is Future) { 57 | // result 58 | // .then((_) => done.finished()) 59 | // .catchError((e) => done.fail(e)); 60 | // } else { 61 | // done.finished(); 62 | // } 63 | // }]); 64 | } 65 | 66 | afterAll(Callback callback) { 67 | context.callMethod('afterAll', [callback]); 68 | } 69 | 70 | class Expectation { 71 | final JsObject obj; 72 | 73 | Expectation(this.obj); 74 | 75 | toBe(dynamic value) { 76 | return obj.callMethod('toBe', [value]); 77 | } 78 | } 79 | 80 | // Jasmine 2.0 81 | // class Done { 82 | // final JsObject obj; 83 | // 84 | // Done(this.obj); 85 | // 86 | // finished() { 87 | // (obj as JsFunction).apply([]); 88 | // } 89 | // 90 | // fail([dynamic error]) { 91 | // if (error != null) { 92 | // obj.callMethod('fail', [error]); 93 | // } else { 94 | // obj.callMethod('fail'); 95 | // } 96 | // } 97 | // } 98 | -------------------------------------------------------------------------------- /spec/_spec/sample-spec.dart: -------------------------------------------------------------------------------- 1 | 2 | import '../_spec/jasmine.dart'; 3 | 4 | void main() { 5 | for (int i in [1, 2, 3]) { 6 | describe('foo sample ${i}', () { 7 | it('is cool', () { 8 | expect(true).toBe(true); 9 | }); 10 | 11 | it('so cool', () { 12 | expect(false).toBe(true); 13 | }); 14 | 15 | for (String str in ['foo', 'bar', 'baz']) { 16 | it('more ${str} cool', () { 17 | expect(true).toBe(true); 18 | }); 19 | } 20 | }); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /spec/_spec/test.dart: -------------------------------------------------------------------------------- 1 | library test; 2 | 3 | import 'jasmine.dart' as jasmine; 4 | 5 | typedef Test(); 6 | 7 | /// TODO: doc 8 | registerSuite(TestSuite testSuite) => _registerSuite(testSuite); 9 | 10 | /// TODO: doc 11 | registerSuites(List testSuites) => testSuites.forEach(registerSuite); 12 | 13 | /// TODO: doc 14 | abstract class TestSuite { 15 | // /// Called once before any test in the test suite is run. 16 | // setUpSuite() { 17 | // 18 | // } 19 | 20 | /// Called before each test in the test suite has run. Can return a Future to 21 | /// indicate that the setup is async. 22 | setUp() { 23 | 24 | } 25 | 26 | /// Return the `` tests for this test suite. 27 | Map getTests(); 28 | 29 | /// Called after each test in the test suite has run. Can return a Future to 30 | /// indicate that the tearDown is async. 31 | tearDown() { 32 | 33 | } 34 | 35 | // /// Called once after all the tests in a test suite has run. 36 | // tearDownSuite() { 37 | // 38 | // } 39 | 40 | /// Used to validate test expectations. 41 | expect(Object actual, Object expected) { 42 | jasmine.expect(actual).toBe(expected); 43 | } 44 | } 45 | 46 | // Impl. 47 | 48 | _registerSuite(TestSuite suite) { 49 | String suiteName = suite.runtimeType.toString(); 50 | 51 | jasmine.describe(suiteName, () { 52 | // // Call setUpSuite. 53 | // jasmine.beforeAll(() { 54 | // return reflect(suite).invoke(#setUpSuite, []); 55 | // }); 56 | 57 | // Call setUp. 58 | jasmine.beforeEach(() => suite.setUp()); 59 | 60 | // Call tearDown. 61 | jasmine.afterEach(() => suite.tearDown()); 62 | 63 | // // Call tearDownSuite. 64 | // jasmine.afterAll(() { 65 | // return reflect(suite).invoke(#tearDownSuite, []); 66 | // }); 67 | 68 | Map tests = suite.getTests(); 69 | 70 | for (String testName in tests.keys) { 71 | Test test = tests[testName]; 72 | print("${suiteName} - ${testName}"); 73 | jasmine.it(testName, () => test()); 74 | } 75 | }); 76 | } 77 | -------------------------------------------------------------------------------- /spec/all-spec.dart: -------------------------------------------------------------------------------- 1 | //import '_spec/animals_test.dart' as animals_test; 2 | //import '_spec/sample-spec.dart' as sample_spec; 3 | 4 | import 'flutter/launch_flutter_test.dart' as launch_flutter_test; 5 | import 'projects_test.dart' as projects_test; 6 | import 'sdk_test.dart' as sdk_test; 7 | 8 | main() { 9 | //animals_test.register(); 10 | //sample_spec.main(); 11 | launch_flutter_test.register(); 12 | projects_test.register(); 13 | sdk_test.register(); 14 | } 15 | -------------------------------------------------------------------------------- /spec/flutter/launch_flutter_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:atom_dart/flutter/flutter_launch.dart'; 2 | 3 | import '../_spec/test.dart'; 4 | 5 | void register() { 6 | registerSuite(new FlutterUriTranslatorTest()); 7 | } 8 | 9 | class FlutterUriTranslatorTest extends TestSuite { 10 | FlutterUriTranslator x = new FlutterUriTranslator('/projects/foo_bar'); 11 | 12 | Map getTests() => { 13 | 'targetToClient_package': _targetToClient_package, 14 | 'targetToClient_file': _targetToClient_file, 15 | 'targetToClient_dart': _targetToClient_dart, 16 | 'clientToTarget_package': _clientToTarget_package, 17 | 'clientToTarget_file': _clientToTarget_file, 18 | 'clientToTarget_dart': _clientToTarget_dart 19 | }; 20 | 21 | _targetToClient_package() { 22 | expect( 23 | x.targetToClient('package:flutter/src/material/dialog.dart'), 24 | 'package:flutter/src/material/dialog.dart' 25 | ); 26 | } 27 | 28 | _targetToClient_file() { 29 | expect( 30 | x.targetToClient('/projects/foo_bar/lib/main.dart'), 31 | '/projects/foo_bar/lib/main.dart' 32 | ); 33 | } 34 | 35 | _targetToClient_dart() { 36 | expect(x.targetToClient('dart:core/core.dart'), 'dart:core/core.dart'); 37 | } 38 | 39 | _clientToTarget_package() { 40 | expect( 41 | x.clientToTarget('package:flutter/src/material/dialog.dart'), 42 | 'package:flutter/src/material/dialog.dart' 43 | ); 44 | } 45 | 46 | _clientToTarget_file() { 47 | expect( 48 | x.clientToTarget('/projects/foo_bar/lib/main.dart'), 49 | '/projects/foo_bar/lib/main.dart' 50 | ); 51 | } 52 | 53 | _clientToTarget_dart() { 54 | expect(x.clientToTarget('dart:core/core.dart'), 'dart:core/core.dart'); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /spec/projects_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:atom_dart/projects.dart'; 2 | import 'package:atom/node/fs.dart'; 3 | 4 | import '_spec/test.dart'; 5 | 6 | void register() => registerSuite(new ProjectsTest()); 7 | 8 | class ProjectsTest extends TestSuite { 9 | Map getTests() => { 10 | // 'isDartBuildFile_findsProject': isDartBuildFile_findsProject, 11 | 'isDartBuildFile_noFalsePositive': isDartBuildFile_noFalsePositives 12 | }; 13 | 14 | isDartBuildFile_findsProject() { 15 | String path = _createTempFile('BUILD', '\n/dart/build_defs\n'); 16 | expect(isDartBuildFile(path), true); 17 | 18 | path = _createTempFile('BUILD', '\ndart_library(\n'); 19 | expect(isDartBuildFile(path), true); 20 | 21 | path = _createTempFile('BUILD', '\ndart_analyzed_library\n'); 22 | expect(isDartBuildFile(path), true); 23 | } 24 | 25 | isDartBuildFile_noFalsePositives() { 26 | String path = _createTempFile('BUILD', '\ndarty\n'); 27 | expect(isDartBuildFile(path), false); 28 | } 29 | } 30 | 31 | String _createTempFile(String name, String contents) { 32 | String path = fs.join(fs.tmpdir, name); 33 | fs.writeFileSync(path, contents); 34 | return path; 35 | } 36 | -------------------------------------------------------------------------------- /spec/sdk_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:atom_dart/sdk.dart'; 2 | 3 | import '_spec/test.dart'; 4 | 5 | void register() => registerSuite(new SdkTest()); 6 | 7 | class SdkTest extends TestSuite { 8 | Map getTests() => { 9 | '_autoConfigure': autoConfigure, 10 | '_sdkDiscovery': sdkDiscovery 11 | }; 12 | 13 | SdkManager _manager; 14 | 15 | setUp() => _manager = new SdkManager(); 16 | tearDown() => _manager?.dispose(); 17 | 18 | autoConfigure() { 19 | return _manager.tryToAutoConfigure().then((result) { 20 | print(result); 21 | expect(result, true); 22 | }); 23 | } 24 | 25 | sdkDiscovery() { 26 | return new SdkDiscovery().discoverSdk().then((String foundSdk) { 27 | print('discoverSdk: ${foundSdk}'); 28 | expect(foundSdk is String, true); 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /styles/console.less: -------------------------------------------------------------------------------- 1 | @import "ui-variables"; 2 | @import "syntax-variables"; 3 | 4 | .atom-view .console-view .tab-scrollable { 5 | overflow-x: auto; 6 | } 7 | 8 | .console-view { 9 | margin: 4px; 10 | 11 | .trace-link { 12 | cursor: pointer; 13 | color: @text-color-highlight; 14 | } 15 | 16 | .trace-link:hover { 17 | text-decoration: underline; 18 | } 19 | 20 | .console-line { 21 | background-color: inherit; 22 | -webkit-user-select: text; 23 | } 24 | 25 | .console-line .badge { 26 | font-size: 12px; 27 | } 28 | 29 | .console-error { 30 | color: @text-color-error; 31 | 32 | .trace-link { 33 | color: @text-color-error; 34 | } 35 | } 36 | 37 | .console-header { 38 | margin-bottom: 0.5em; 39 | } 40 | 41 | .console-footer { 42 | margin-top: 0.5em; 43 | font-style: italic; 44 | color: @text-color-subtle; 45 | } 46 | } 47 | 48 | .atom-view .launch-terminated .title { 49 | font-style: italic; 50 | } 51 | 52 | .process-status-bar { 53 | .badge { 54 | color: @text-color; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /styles/dartdoc.less: -------------------------------------------------------------------------------- 1 | @import "ui-variables"; 2 | @import "syntax-variables"; 3 | 4 | @font-face { 5 | font-family: 'Material Icons'; 6 | font-style: normal; 7 | font-weight: 400; 8 | src: local('Material Icons'), local('MaterialIcons-Regular'), url('https://fonts.gstatic.com/s/materialicons/v13/2fcrYFNaTjcS6g4U3t-Y5UEw0lE80llgEseQY3FEmqw.woff2') format('woff2'); 9 | } 10 | 11 | // Allow the material icons font in the doc tooltips and code completions. 12 | #dartdoc-tooltip, 13 | autocomplete-suggestion-list { 14 | .material-icons { 15 | font-family: 'Material Icons'; 16 | font-weight: normal; 17 | font-style: normal; 18 | font-size: 24px; 19 | line-height: 1; 20 | vertical-align: bottom; 21 | letter-spacing: normal; 22 | text-transform: none; 23 | display: inline-block; 24 | white-space: nowrap; 25 | word-wrap: normal; 26 | direction: ltr; 27 | -webkit-font-feature-settings: 'liga'; 28 | -webkit-font-smoothing: antialiased; 29 | } 30 | } 31 | 32 | #dartdoc-tooltip { 33 | position: absolute; 34 | top: 8px; 35 | right: 8px; 36 | left: inherit; 37 | bottom: inherit; 38 | width: 445px; 39 | padding: 0; 40 | color: @text-color; 41 | border-radius: 2px; 42 | z-index: 100; 43 | font-size: 110%; 44 | border: none; 45 | 46 | .dartdoc-title { 47 | color: @text-color-highlight; 48 | background-color: @panel-heading-background-color; 49 | border-top-left-radius: 2px; 50 | border-top-right-radius: 2px; 51 | padding: 4px 8px; 52 | } 53 | 54 | .dartdoc-footer { 55 | color: @text-color-highlight; 56 | background-color: @panel-heading-background-color; 57 | border-bottom-left-radius: 2px; 58 | border-bottom-right-radius: 2px; 59 | padding: 4px 8px; 60 | border-bottom: 0px; 61 | } 62 | 63 | .dartdoc-body { 64 | padding: 0 8px; 65 | min-height: 100px; 66 | max-height: 300px; 67 | overflow-y: scroll; 68 | position: relative; 69 | background-color: @tree-view-background-color; 70 | 71 | p { 72 | margin: 8px 0; 73 | } 74 | 75 | li p { 76 | margin: 0; 77 | } 78 | 79 | pre { 80 | margin: 0 16px; 81 | } 82 | 83 | h1, h2, h3 { 84 | margin-bottom: 0; 85 | } 86 | 87 | pre { 88 | color: @syntax-text-color; 89 | background-color: @syntax-background-color; 90 | text-shadow: none; 91 | } 92 | 93 | pre code { 94 | word-wrap: normal; 95 | white-space: pre; 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /styles/debugger.less: -------------------------------------------------------------------------------- 1 | @import "ui-variables"; 2 | @import "syntax-variables"; 3 | 4 | .debugger { 5 | margin: 4px; 6 | 7 | .button-bar { 8 | min-height: 22px; 9 | } 10 | 11 | .debugger-execution-toolbar { 12 | display: flex; 13 | margin-left: -3px; 14 | overflow-x: hidden; 15 | margin-top: 6px; 16 | 17 | button.btn { 18 | margin-left: 3px; 19 | } 20 | } 21 | 22 | .debugger-section { 23 | flex-shrink: 0; 24 | border-bottom: 1px solid @base-border-color; 25 | position: relative; 26 | padding-top: 8px; 27 | padding-bottom: 6px; 28 | 29 | .debugger-section-subtitle { 30 | color: @text-color-subtle; 31 | overflow: hidden; 32 | white-space: nowrap; 33 | text-overflow: ellipsis; 34 | margin-top: 5px; 35 | } 36 | 37 | label { 38 | margin-right: 5px; 39 | } 40 | } 41 | 42 | .debugger-section.view-header { 43 | padding-top: 0; 44 | } 45 | 46 | .debugger-section.debugger-section-last { 47 | border-bottom: none; 48 | padding-bottom: 0; 49 | } 50 | 51 | .debugger-section.resizable { 52 | min-width: 100px; 53 | min-height: 100px; 54 | } 55 | 56 | .debug-tab-container { 57 | flex-shrink: 0; 58 | 59 | .debug-tab-title { 60 | color: @text-color-subtle; 61 | align-self: flex-end; 62 | line-height: 22px; 63 | min-height: 22px; 64 | } 65 | 66 | .debug-tab-toolbar { 67 | min-height: 22px; 68 | } 69 | } 70 | 71 | .debugger-breakpoint-path { 72 | 73 | } 74 | 75 | .debugger-breakpoint-icon { 76 | color: @syntax-gutter-text-color; 77 | opacity: 0.6; 78 | } 79 | 80 | .debugger-secondary-info { 81 | margin-left: 0.5em; 82 | color: @text-color-subtle; 83 | } 84 | 85 | .material-list-selected .debugger-secondary-info { 86 | color: @text-color; 87 | } 88 | 89 | .debugger-frame-area { 90 | height: 156px; 91 | margin-bottom: 6px; 92 | border-bottom: 1px solid @base-border-color; 93 | } 94 | 95 | .debugger-local-area { 96 | transition: background-color 400ms; 97 | } 98 | 99 | .debugger-locked { 100 | pointer-events: none; 101 | background-color: @base-border-color; 102 | } 103 | 104 | .debugger-object-details { 105 | min-height: 1em; 106 | max-height: 5em; 107 | overflow: auto; 108 | } 109 | 110 | .right-aligned { 111 | text-align: right; 112 | } 113 | 114 | label + label { 115 | margin-left: 10px; 116 | } 117 | 118 | label { 119 | input + span { 120 | margin-left: 8px; 121 | } 122 | } 123 | } 124 | 125 | atom-text-editor.editor .debugger-breakpoint { 126 | 127 | } 128 | 129 | atom-text-editor.editor .debugger-breakpoint::before { 130 | font-family: 'Octicons Regular'; 131 | font-weight: normal; 132 | font-style: normal; 133 | display: inline-block; 134 | line-height: 1; 135 | text-decoration: none; 136 | font-size: 16px; 137 | width: 16px; 138 | height: 16px; 139 | content: "\f052"; 140 | position: absolute; 141 | top: 3px; 142 | right: 5px; 143 | -webkit-font-smoothing: antialiased; 144 | } 145 | 146 | atom-text-editor.editor .debugger-executionpoint-line { 147 | background-color: @syntax-selection-color; 148 | } 149 | 150 | atom-text-editor.editor .debugger-executionpoint-highlight div.region { 151 | z-index: 1; 152 | } 153 | 154 | atom-text-editor.editor .debugger-executionpoint-highlight div.region::before { 155 | font-family: 'Octicons Regular'; 156 | font-weight: normal; 157 | font-style: normal; 158 | display: inline-block; 159 | line-height: 1; 160 | text-decoration: none; 161 | font-size: 12px; 162 | width: 16px; 163 | height: 16px; 164 | content: "\f0aa"; 165 | position: absolute; 166 | top: 16px; 167 | right: -4px; 168 | -webkit-font-smoothing: antialiased; 169 | } 170 | 171 | atom-text-editor.editor .debugger-executionpoint-linenumber { 172 | 173 | } 174 | 175 | atom-text-editor.editor .debugger-executionpoint-linenumber::before { 176 | font-family: 'Octicons Regular'; 177 | font-weight: normal; 178 | font-style: normal; 179 | display: inline-block; 180 | line-height: 1; 181 | text-decoration: none; 182 | font-size: 18px; 183 | width: 16px; 184 | height: 16px; 185 | content: "\f03e"; 186 | position: absolute; 187 | top: 1px; 188 | right: 2px; 189 | -webkit-font-smoothing: antialiased; 190 | } 191 | -------------------------------------------------------------------------------- /styles/material.less: -------------------------------------------------------------------------------- 1 | @import "ui-variables"; 2 | 3 | .material-icon-button { 4 | display: inline-block; 5 | 6 | width: 22px; 7 | margin: 1px; 8 | text-align: center; 9 | border-radius: 50%; 10 | transition: background-color 40ms ease; 11 | 12 | span { 13 | display: inline-block; 14 | margin: 3px; 15 | } 16 | } 17 | 18 | .material-icon-button:hover { 19 | background-color: @background-color-highlight; 20 | color: @text-color-highlight; 21 | } 22 | 23 | .material-icon-button:active { 24 | background-color: @background-color-selected; 25 | } 26 | 27 | .material-icon-button[disabled] { 28 | color: @text-color-subtle; 29 | background-color: inherit !important; 30 | } 31 | 32 | .material-tabgroup { 33 | .material-tab-container { 34 | overflow-x: hidden; 35 | display: flex; 36 | margin-bottom: 2px; 37 | } 38 | 39 | .material-tab { 40 | font-size: 1.1em; 41 | text-transform: uppercase; 42 | cursor: default; 43 | display: inline; 44 | } 45 | 46 | .material-tab + .material-tab { 47 | margin-left: 0.8em; 48 | } 49 | 50 | .material-tab.tab-selected { 51 | color: @text-color-highlight; 52 | } 53 | 54 | .material-tab:hover { 55 | color: @text-color-highlight; 56 | text-decoration: underline; 57 | } 58 | 59 | .material-tab[disabled] { 60 | color: @text-color-subtle; 61 | text-decoration: none; 62 | background-color: inherit; 63 | } 64 | } 65 | 66 | .material-list { 67 | ul, 68 | ol { 69 | margin-bottom: 0; 70 | padding-left: 0; 71 | overflow-y: auto; 72 | overflow-x: hidden; 73 | cursor: default; 74 | } 75 | 76 | li { 77 | cursor: pointer; 78 | display: block; 79 | line-height: @component-line-height; 80 | min-height: @component-line-height; 81 | padding: 0 4px; 82 | white-space: nowrap; 83 | text-overflow: ellipsis; 84 | overflow-x: hidden; 85 | 86 | .material-icon-button span { 87 | margin: 0; 88 | } 89 | } 90 | 91 | li:hover { 92 | background-color: @button-background-color-hover; 93 | } 94 | 95 | li.material-list-selected { 96 | color: @text-color-selected; 97 | background-color: @background-color-selected; 98 | } 99 | 100 | ul.material-list-indent { 101 | margin-left: @component-icon-padding + @component-icon-size; 102 | } 103 | 104 | .icon-triangle-right::before { 105 | position: relative; 106 | left: 3px; 107 | } 108 | } 109 | 110 | .hideable { 111 | opacity: 1; 112 | transition: opacity 100ms ease; 113 | } 114 | 115 | .hideable.hiding { 116 | opacity: 0; 117 | } 118 | -------------------------------------------------------------------------------- /styles/outline.less: -------------------------------------------------------------------------------- 1 | @import "ui-variables"; 2 | @import "syntax-variables"; 3 | 4 | .outline-view { 5 | border-left: 1px solid @tool-panel-border-color; 6 | 7 | order: 2; 8 | min-width: 175px; 9 | overflow-x: hidden; 10 | 11 | display: flex; 12 | position: relative; 13 | flex-direction: column; 14 | 15 | .title-container { 16 | border-bottom: 1px solid @tool-panel-border-color; 17 | margin: 6px; 18 | padding-bottom: 6px; 19 | display: flex; 20 | flex-direction: row; 21 | flex-shrink: 0; 22 | } 23 | 24 | .title-text { 25 | cursor: default; 26 | text-overflow: ellipsis; 27 | overflow-x: hidden; 28 | flex: 20; 29 | white-space: pre; 30 | } 31 | 32 | .outline-tree { 33 | flex: 20; 34 | padding: 0 6px 6px 6px; 35 | overflow-y: auto; 36 | } 37 | 38 | .outline-errors { 39 | margin: 6px; 40 | border-top: 1px solid @tool-panel-border-color; 41 | padding-top: 6px; 42 | } 43 | 44 | .errors-list { 45 | max-height: 82px; 46 | overflow-x: hidden; 47 | overflow-y: auto; 48 | 49 | .outline-error-item { 50 | cursor: pointer; 51 | overflow-x: hidden; 52 | white-space: nowrap; 53 | text-overflow: ellipsis; 54 | display: flex; 55 | 56 | .item-info { 57 | color: @syntax-color-renamed; 58 | flex: 0 0 auto; 59 | } 60 | 61 | .item-warning { 62 | color: @syntax-color-modified; 63 | flex: 0 0 auto; 64 | } 65 | 66 | .item-error { 67 | color: @syntax-color-removed; 68 | flex: 0 0 auto; 69 | } 70 | 71 | .item-text { 72 | flex: 1 1 auto; 73 | overflow-x: inherit; 74 | text-overflow: inherit; 75 | padding-left: 8px; 76 | } 77 | 78 | .item-icon { 79 | flex: 0 0 auto; 80 | } 81 | } 82 | } 83 | 84 | ul, 85 | ol { 86 | padding-left: 12px; 87 | } 88 | 89 | ol, 90 | ul, 91 | li { 92 | list-style: none; 93 | margin-top: 0; 94 | margin-bottom: 0; 95 | } 96 | 97 | li { 98 | cursor: default; 99 | text-overflow: ellipsis; 100 | overflow-x: hidden; 101 | } 102 | 103 | .list-item { 104 | white-space: nowrap; 105 | } 106 | 107 | .list-item.region { 108 | font-weight: bold; 109 | background-color: @background-color-selected; 110 | } 111 | 112 | .list-item:hover { 113 | background-color: @background-color-highlight; 114 | } 115 | 116 | .outline-deprecated { 117 | text-decoration: line-through; 118 | } 119 | 120 | .outline-static { 121 | font-style: italic; 122 | } 123 | 124 | .muted { 125 | color: @text-color-subtle; 126 | } 127 | 128 | a { 129 | cursor: pointer; 130 | } 131 | 132 | a:hover { 133 | text-decoration: underline; 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /styles/status.less: -------------------------------------------------------------------------------- 1 | @import "ui-variables"; 2 | 3 | .plugin-status { 4 | margin: 4px; 5 | 6 | .view-section { 7 | transition: color 0.4s, background-color 0.4s; 8 | 9 | .status-header { 10 | margin-bottom: 6px; 11 | overflow-x: hidden; 12 | text-overflow: ellipsis; 13 | display: flex; 14 | align-items: baseline; 15 | 16 | .view-subtitle { 17 | margin-left: 0.3em; 18 | } 19 | } 20 | 21 | button { 22 | margin-left: 6px; 23 | } 24 | 25 | div.bottom-margin { 26 | margin-bottom: 6px; 27 | } 28 | 29 | .diagnostics-title { 30 | display: inline-block; 31 | min-width: 8em; 32 | text-align: right; 33 | } 34 | 35 | .diagnostics-data { 36 | margin-left: 0.5em; 37 | color: @text-color-subtle; 38 | } 39 | } 40 | 41 | .view-section.status-emphasis { 42 | background-color: @background-color-highlight; 43 | 44 | .status-header { 45 | color: @text-color-highlight; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /styles/tooltip.less: -------------------------------------------------------------------------------- 1 | #hover-tooltip { 2 | background-color: #226AEF; 3 | border-radius: 2px; 4 | bottom: 0px; 5 | box-shadow: 0px 0px 8px #13213C; 6 | color: #FFF; 7 | font-size: 14px; 8 | left: 0px; 9 | padding: 5px 10px; 10 | position: absolute; 11 | right: inherit; 12 | top: inherit; 13 | z-index: 10000; 14 | display: flex; 15 | flex-direction: column; 16 | } 17 | 18 | #hover-tooltip .debugger-data { 19 | display: flex; 20 | flex-direction: column; 21 | } 22 | 23 | #hover-tooltip .debugger-data { 24 | flex-basis: 26px; 25 | } 26 | 27 | #hover-tooltip .debugger-data.expandable { 28 | flex-basis: 148px; 29 | } 30 | 31 | #hover-tooltip ::-webkit-scrollbar-track { 32 | background: transparent; 33 | } 34 | 35 | #hover-tooltip .material-list { 36 | ul, 37 | ol { 38 | margin-bottom: 0; 39 | padding-left: 0; 40 | overflow-y: auto; 41 | overflow-x: hidden; 42 | cursor: default; 43 | } 44 | 45 | li { 46 | cursor: pointer; 47 | display: block; 48 | line-height: @component-line-height; 49 | min-height: @component-line-height; 50 | padding: 0 4px; 51 | white-space: nowrap; 52 | text-overflow: ellipsis; 53 | overflow-x: hidden; 54 | 55 | .material-icon-button span { 56 | margin: 0; 57 | } 58 | } 59 | 60 | li:hover { 61 | background-color: transparent; 62 | } 63 | 64 | li.material-list-selected { 65 | color: @text-color-selected; 66 | background-color: transparent; 67 | } 68 | 69 | ul.material-list-indent { 70 | margin-left: @component-icon-padding + @component-icon-size; 71 | } 72 | 73 | .icon-triangle-right::before { 74 | position: relative; 75 | left: 3px; 76 | } 77 | 78 | .debugger-secondary-info { 79 | margin-left: 0.5em; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /styles/views.less: -------------------------------------------------------------------------------- 1 | @import "ui-variables"; 2 | 3 | div.atom-view { 4 | min-width: 100px; 5 | min-height: 64px; 6 | height: 100%; 7 | padding-left: 0; 8 | padding-right: 0; 9 | } 10 | 11 | .atom-view { 12 | // Contains zero or more tab contents. 13 | .tab-container { 14 | padding: 0 8px; 15 | display: flex; 16 | } 17 | 18 | // The style for content contained in a tab. 19 | .tab-content { 20 | position: relative; 21 | flex: 1; 22 | display: flex; 23 | flex-direction: column; 24 | width: 100%; 25 | } 26 | 27 | .tab-scrollable-container { 28 | display: flex; 29 | flex-direction: column; 30 | overflow: hidden; 31 | } 32 | 33 | // A scrollable area in a tab view. 34 | .tab-scrollable { 35 | overflow-y: auto; 36 | overflow-x: hidden; 37 | flex: 1; 38 | padding: 8px 0; 39 | } 40 | 41 | .tab-non-scrollable { 42 | flex: 1; 43 | padding: 8px 0; 44 | } 45 | 46 | .button-bar { 47 | position: absolute; 48 | top: 8px; 49 | right: 16px; 50 | display: flex; 51 | align-items: center; 52 | z-index: 1; 53 | 54 | .icon::before { 55 | font-size: 14px; 56 | width: 14px; 57 | height: 14px; 58 | } 59 | } 60 | 61 | .view-header { 62 | padding-bottom: 6px; 63 | border-bottom: 1px solid @base-border-color; 64 | flex-shrink: 0; 65 | 66 | .view-subtitle { 67 | margin-top: 2px; 68 | } 69 | } 70 | 71 | .view-title { 72 | font-size: 1.25em; 73 | overflow: hidden; 74 | white-space: nowrap; 75 | text-overflow: ellipsis; 76 | } 77 | 78 | .view-subtitle { 79 | color: @text-color-subtle; 80 | overflow-x: hidden; 81 | white-space: nowrap; 82 | text-overflow: ellipsis; 83 | } 84 | 85 | .view-header-static { 86 | margin-top: 8px; 87 | } 88 | 89 | .view-resize[vertical] { 90 | cursor: ew-resize; 91 | z-index: 3; 92 | position: absolute;; 93 | top: 0; 94 | bottom: 0; 95 | width: 8px; 96 | } 97 | 98 | .view-resize[horizontal] { 99 | cursor: ns-resize; 100 | z-index: 3; 101 | position: absolute;; 102 | left: 0; 103 | right: 0; 104 | top: 0; 105 | height: 8px; 106 | } 107 | 108 | .view-resize[horizontal][top] { 109 | top: inherit; 110 | bottom: 0; 111 | } 112 | 113 | .view-section { 114 | padding-bottom: 8px; 115 | margin-bottom: 8px; 116 | border-bottom: 1px solid @base-border-color; 117 | 118 | .view-title { 119 | font-size: 1.1em; 120 | } 121 | 122 | .view-section-buttons { 123 | text-align: end; 124 | margin-top: 6px; 125 | 126 | button { 127 | margin-left: 6px; 128 | } 129 | } 130 | } 131 | 132 | .view-section.view-section-last { 133 | padding-bottom: 0; 134 | margin-bottom: 0; 135 | border-bottom: none; 136 | } 137 | } 138 | 139 | .atom-view[compact] { 140 | .tab-container { 141 | padding: 0 4px; 142 | } 143 | 144 | .tab-scrollable { 145 | padding: 4px 0; 146 | } 147 | } 148 | 149 | .atom-view { 150 | .list-tree { 151 | .list-item, 152 | li.list-nested-item div.list-item { 153 | text-overflow: ellipsis; 154 | overflow-x: hidden; 155 | white-space: nowrap; 156 | line-height: 22px; 157 | } 158 | 159 | li.list-nested-item.collapsed > div.list-item::before { 160 | content: "\f078"; 161 | } 162 | 163 | li.list-nested-item.collapsed > ul.list-tree { 164 | display: none; 165 | } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /test/all.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library atom.all_test; 6 | 7 | import 'dartino_util_test.dart' as dartino_util_test; 8 | import 'dependencies_test.dart' as dependencies_test; 9 | import 'evaluator_test.dart' as evaluator_test; 10 | import 'testing_utils_test.dart' as testing_utils_test; 11 | import 'utils_test.dart' as utils_test; 12 | 13 | main() { 14 | dartino_util_test.defineTests(); 15 | dependencies_test.defineTests(); 16 | evaluator_test.defineTests(); 17 | testing_utils_test.defineTests(); 18 | utils_test.defineTests(); 19 | } 20 | -------------------------------------------------------------------------------- /test/dartino_util_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library atom.dartino_util_test; 6 | 7 | import 'package:atom_dart/dartino/dartino_util.dart'; 8 | import 'package:test/test.dart'; 9 | 10 | main() => defineTests(); 11 | 12 | defineTests() { 13 | group('dartino', () { 14 | group('packages file', () { 15 | test('random', () { 16 | expect(containsDartinoReferences(null, null), isFalse); 17 | expect(containsDartinoReferences(null, ''), isFalse); 18 | expect(containsDartinoReferences('', null), isFalse); 19 | expect(containsDartinoReferences(null, ''), isFalse); 20 | expect(containsDartinoReferences(null, 'foo'), isFalse); 21 | expect(containsDartinoReferences('foo', 'bar'), isFalse); 22 | expect(containsDartinoReferences('asd aesfse', 'asd'), isFalse); 23 | }); 24 | test('non Dartino package file', () { 25 | bool actual = containsDartinoReferences( 26 | '''# Generated by pub 27 | analyzer:file:///Users/foo/bar/lib/ 28 | ansicolor:file:///Users/foo/two/lib/ 29 | args:file:///Users/foo/three/lib/ 30 | async:file:///Users/foo/four/lib/ 31 | ''', 32 | '/path/to/dartino-sdk'); 33 | expect(actual, isFalse); 34 | }); 35 | test('Dartino package file', () { 36 | bool actual = containsDartinoReferences( 37 | '''# Generated by pub 38 | analyzer:file:///Users/foo/bar/lib/ 39 | ansicolor:file:///path/to/dartino-sdk/pkg/dartino/lib/ 40 | args:file:///Users/foo/three/lib/ 41 | async:file:///Users/foo/four/lib/ 42 | ''', 43 | '/path/to/dartino-sdk'); 44 | expect(actual, isTrue); 45 | }); 46 | }); 47 | }); 48 | } 49 | -------------------------------------------------------------------------------- /test/dependencies_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library atom.dependencies_test; 6 | 7 | import 'package:atom/utils/dependencies.dart'; 8 | import 'package:test/test.dart'; 9 | 10 | main() => defineTests(); 11 | 12 | defineTests() { 13 | group('dependencies', () { 14 | test('retrieve dependency', () { 15 | Dependencies dependency = new Dependencies(); 16 | expect(dependency[String], isNull); 17 | dependency[String] = 'foo'; 18 | expect(dependency[String], isNotNull); 19 | expect(dependency[String], 'foo'); 20 | }); 21 | 22 | test('runInZone', () { 23 | expect(Dependencies.instance, isNull); 24 | Dependencies dependency = new Dependencies(); 25 | expect(Dependencies.instance, isNull); 26 | dependency[String] = 'foo'; 27 | dependency.runInZone(() { 28 | expect(Dependencies.instance, isNotNull); 29 | expect(dependency[String], 'foo'); 30 | }); 31 | expect(Dependencies.instance, isNull); 32 | }); 33 | }); 34 | } 35 | -------------------------------------------------------------------------------- /test/evaluator_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library atom.evaluator_test; 6 | 7 | import 'dart:async'; 8 | 9 | import 'package:test/test.dart'; 10 | import 'package:petitparser/petitparser.dart'; 11 | 12 | import '../lib/debug/evaluator.dart'; 13 | 14 | main() => defineTests(); 15 | 16 | defineTests() { 17 | group('Evaluator', () { 18 | Future testExpression(String input, [String expected]) async { 19 | EvaluatorReverseParser parser = new EvaluatorReverseParser(); 20 | dynamic eval = parser.parse(parser.reverseString(input), 1000); 21 | EvalExpression expression = new EvalExpression('file.dart', eval); 22 | Evaluator evaluator = new Evaluator(expression); 23 | String result = await evaluator.eval(); 24 | expect(result, expected ?? input); 25 | } 26 | 27 | Future failParse(String input) async { 28 | EvaluatorReverseParser parser = new EvaluatorReverseParser(); 29 | expect(parser.reverseContextParser.parse(parser.reverseString(input)), 30 | new isInstanceOf()); 31 | } 32 | 33 | test('Testing a', () => testExpression('a')); 34 | test('Testing !a', () => testExpression('!a', 'a')); 35 | test('Testing a.b', () => testExpression('a.b')); 36 | test('Testing a.b.c', () => testExpression('a.b.c')); 37 | test('Testing a. b . c', () => testExpression('a. b . c', 'a.b.c')); 38 | test('Testing a . !b . c', () => testExpression('a . !b . c', 'b.c')); 39 | test('Testing a[c].b', () => testExpression('a[c].b')); 40 | test('Testing a[1].b', () => testExpression('a[1].b')); 41 | test('Testing a[1].b[1]', () => testExpression('a[1].b[1]')); 42 | test('Testing a[b[2]].c', () => testExpression('a[b[2]].c')); 43 | 44 | test('Failing on !', () => failParse('!')); 45 | // reverse parser we should never have a non id at the right 46 | test('Failing on a!', () => failParse('a!')); 47 | }); 48 | } 49 | -------------------------------------------------------------------------------- /test/testing_utils_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:atom_dart/impl/testing_utils.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | main() => defineTests(); 5 | 6 | defineTests() { 7 | group('test_utils', () { 8 | test('getPossibleTestPaths', () { 9 | expect(getPossibleTestPaths('bin/foo.dart', '/'), equals([])); 10 | expect( 11 | getPossibleTestPaths('lib/foo.dart', '/'), 12 | equals(['test/foo_test.dart']) 13 | ); 14 | expect( 15 | getPossibleTestPaths('lib/bar/foo.dart', '/'), 16 | equals(['test/bar/foo_test.dart', 'test/foo_test.dart']) 17 | ); 18 | expect( 19 | getPossibleTestPaths('lib/src/foo.dart', '/'), 20 | equals(['test/src/foo_test.dart', 'test/foo_test.dart']) 21 | ); 22 | expect( 23 | getPossibleTestPaths('lib/src/bar/foo.dart', '/'), 24 | equals(['test/src/bar/foo_test.dart', 'test/bar/foo_test.dart', 'test/foo_test.dart']) 25 | ); 26 | }); 27 | }); 28 | } 29 | -------------------------------------------------------------------------------- /test/utils_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library atom.utils_test; 6 | 7 | import 'dart:async'; 8 | 9 | import 'package:atom/utils/string_utils.dart'; 10 | import 'package:atom_dart/utils.dart'; 11 | import 'package:test/test.dart'; 12 | 13 | main() => defineTests(); 14 | 15 | defineTests() { 16 | group('utils', () { 17 | test('toStartingLowerCase', () { 18 | expect(toStartingLowerCase(''), ''); 19 | expect(toStartingLowerCase('a'), 'a'); 20 | expect(toStartingLowerCase('A'), 'a'); 21 | expect(toStartingLowerCase('ABC'), 'aBC'); 22 | expect(toStartingLowerCase('abc'), 'abc'); 23 | }); 24 | 25 | test('simpleDiff 1', () { 26 | _checkDiff(simpleDiff('aabcc', 'aacc'), new Edit(2, 1, '')); 27 | }); 28 | 29 | test('simpleDiff 2', () { 30 | _checkDiff(simpleDiff('aaa', 'bbb'), new Edit(0, 3, 'bbb')); 31 | }); 32 | 33 | test('simpleDiff 3', () { 34 | _checkDiff(simpleDiff('aabb', 'aabbc'), new Edit(4, 0, 'c')); 35 | }); 36 | 37 | test('simpleDiff 4', () { 38 | _checkDiff(simpleDiff('abbb', 'bbb'), new Edit(0, 1, '')); 39 | }); 40 | 41 | test('simpleDiff 5', () { 42 | _checkDiff(simpleDiff('aabb', 'aabb'), new Edit(0, 0, '')); 43 | }); 44 | 45 | test('simpleDiff 6', () { 46 | _checkDiff(simpleDiff('', 'aabb'), new Edit(0, 0, 'aabb')); 47 | }); 48 | 49 | test('simpleDiff 7', () { 50 | _checkDiff(simpleDiff('aabb', ''), new Edit(0, 4, '')); 51 | }); 52 | }); 53 | 54 | group('Property', () { 55 | test('mutate value', () { 56 | Property p = new Property(); 57 | expect(p.value, null); 58 | p.value = 123; 59 | expect(p.value, 123); 60 | }); 61 | 62 | test('mutation fires event', () { 63 | Property p = new Property(); 64 | expect(p.value, null); 65 | Future f = p.onChanged.first; 66 | p.value = '123'; 67 | expect(p.value, '123'); 68 | return f.then((val) => expect(val, '123')); 69 | }); 70 | }); 71 | 72 | group('SelectionGroup', () { 73 | test('adding changes selection', () { 74 | SelectionGroup group = new SelectionGroup(); 75 | Future f = group.onSelectionChanged.first.then((sel) { 76 | expect(sel, 'foo'); 77 | }); 78 | group.add('foo'); 79 | return f; 80 | }); 81 | 82 | test('removing changes selection', () { 83 | SelectionGroup group = new SelectionGroup(); 84 | group.add('foo'); 85 | Future f = group.onSelectionChanged.first.then((sel) { 86 | expect(sel, null); 87 | }); 88 | group.remove('foo'); 89 | return f; 90 | }); 91 | }); 92 | } 93 | 94 | _checkDiff(List edits, Edit expectEdit) { 95 | expect(edits.length, 1); 96 | expect(edits.first, expectEdit); 97 | } 98 | -------------------------------------------------------------------------------- /tool/grind.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library atom.grind; 6 | 7 | import 'dart:io'; 8 | 9 | import 'package:grinder/grinder.dart'; 10 | import 'package:which/which.dart'; 11 | 12 | import 'package:atom/build/build.dart'; 13 | import 'package:atom/build/publish.dart'; 14 | 15 | main(List args) => grind(args); 16 | 17 | @Task() 18 | analyze() => new PubApp.global('tuneup').runAsync(['check', '--ignore-infos']); 19 | 20 | @DefaultTask() 21 | build() async { 22 | File inputFile = getFile('web/entry.dart'); 23 | File outputFile = getFile('web/entry.dart.js'); 24 | 25 | // --trust-type-annotations? --trust-primitives? 26 | await Dart2js.compileAsync(inputFile, csp: true, extraArgs: ['--show-package-warnings']); 27 | outputFile.writeAsStringSync(patchDart2JSOutput(outputFile.readAsStringSync())); 28 | } 29 | 30 | @Task('Build the Atom tests') 31 | buildAtomTests() async { 32 | final String base = 'spec/all-spec'; 33 | File inputFile = getFile('${base}.dart'); 34 | File outputFile = getFile('${base}.js'); 35 | await Dart2js.compileAsync(inputFile, csp: true, outFile: outputFile); 36 | delete(getFile('${base}.js.deps')); 37 | } 38 | 39 | @Task('Run the Atom tests') 40 | @Depends(buildAtomTests) 41 | runAtomTests() async { 42 | String apmPath = whichSync('apm', orElse: () => null); 43 | 44 | if (apmPath != null) { 45 | await runAsync('apm', arguments: ['test']); 46 | } else { 47 | log("warning: command 'apm' not found"); 48 | } 49 | } 50 | 51 | @Task() 52 | @Depends(build) //analyze, build, test, runAtomTests) 53 | publish() => publishAtomPlugin(); 54 | 55 | @Task() 56 | test() => Dart.runAsync('test/all.dart'); 57 | 58 | @Task() 59 | @Depends(analyze, build, test, runAtomTests) 60 | bot() => null; 61 | 62 | @Task() 63 | clean() { 64 | delete(getFile('web/entry.dart.js')); 65 | delete(getFile('web/entry.dart.js.deps')); 66 | delete(getFile('web/entry.dart.js.map')); 67 | } 68 | 69 | @Task('generate the analysis server API') 70 | analysisApi() { 71 | // https://github.com/dart-lang/sdk/blob/master/pkg/analysis_server/tool/spec/spec_input.html 72 | Dart.run('tool/analysis/generate_analysis.dart', packageRoot: 'packages'); 73 | DartFmt.format('lib/analysis/analysis_server_lib.dart', lineLength: 90); 74 | } 75 | 76 | @Task() 77 | @Depends(analysisApi) 78 | generate() => null; 79 | -------------------------------------------------------------------------------- /tool/source_map.dart: -------------------------------------------------------------------------------- 1 | library source_map; 2 | 3 | import 'dart:async'; 4 | import 'dart:convert' show LineSplitter, UTF8; 5 | import 'dart:io'; 6 | 7 | import 'package:source_maps/source_maps.dart'; 8 | 9 | void main(List args) { 10 | Map> files = {}; 11 | Map maps = {}; 12 | 13 | List futures = []; 14 | //for (var file in ['main.dart', 'main.dart.js', 'main.dart.js.map']) { 15 | for (var file in ['main.dart', 'web__main.js', 'web__main.js.map']) { 16 | futures.add(getFile(file).then((lines) => files[file] = lines)); 17 | } 18 | Future.wait(futures).then((_) { 19 | for (var file in files.keys) { 20 | if (file.endsWith('.map')) { 21 | SingleMapping map = parse(files[file].join()); 22 | maps[file.substring(0, file.length - 4)] = map; 23 | } 24 | } 25 | for (var file in maps.keys) { 26 | SingleMapping map = maps[file]; 27 | List source = files[file]; 28 | Set unknownUrls = new Set(); 29 | 30 | for (var line in map.lines) { 31 | if (line.entries.isEmpty) continue; 32 | bool output = false; 33 | for (var entry in line.entries) { 34 | if (entry.sourceUrlId == null || entry.sourceUrlId < 0) continue; 35 | String destinationFile = map.urls[entry.sourceUrlId]; 36 | if (files[destinationFile] != null) { 37 | if (!output) { 38 | print('${line.line}:${source[line.line]}'); 39 | output = true; 40 | } 41 | List destination = files[destinationFile]; 42 | print('->@${entry.column}[$destinationFile:${entry.sourceLine},${entry.sourceColumn}]' 43 | '${destination[entry.sourceLine]}'); 44 | if (entry.sourceNameId != null && entry.sourceNameId >= 0) { 45 | print(' ${map.names[entry.sourceNameId]}'); 46 | } 47 | } else { 48 | unknownUrls.add(destinationFile); 49 | } 50 | } 51 | } 52 | print('UNKNOWN URLS: $unknownUrls'); 53 | } 54 | }).catchError((e) { 55 | print(e); 56 | }); 57 | } 58 | 59 | Future> getFile(String filename) { 60 | HttpClient client = new HttpClient(); 61 | return client.get('localhost', 8081, filename).then((request) { 62 | request.headers.contentType 63 | = new ContentType("text", "plain", charset: "utf-8"); 64 | return request.close(); 65 | }).then((response) { 66 | return UTF8.decodeStream(response); 67 | }).then((t) { 68 | var ret = LineSplitter.split(t).toList(); 69 | print('loaded: $filename (${ret.length})'); 70 | return ret; 71 | }); 72 | 73 | } 74 | -------------------------------------------------------------------------------- /tool/src/parser.dart: -------------------------------------------------------------------------------- 1 | 2 | library parser; 3 | 4 | import 'src_gen.dart'; 5 | 6 | class Token { 7 | static final RegExp _alpha = new RegExp(r'^[0-9a-zA-Z_\-@]+$'); 8 | 9 | final String text; 10 | Token next; 11 | 12 | Token(this.text); 13 | 14 | bool get eof => text == null; 15 | 16 | bool get isName { 17 | if (text == null || text.isEmpty) return false; 18 | return _alpha.hasMatch(text); 19 | } 20 | 21 | bool get isComment => text != null && text.startsWith('//'); 22 | 23 | String toString() => text == null ? 'EOF' : text; 24 | } 25 | 26 | class Tokenizer { 27 | static final alphaNum = 28 | '@abcdefghijklmnopqrstuvwxyz-_0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'; 29 | static final whitespace = ' \n\t\r'; 30 | 31 | String text; 32 | Token _head; 33 | Token _last; 34 | 35 | Tokenizer(this.text); 36 | 37 | Token tokenize() { 38 | _emit(null); 39 | 40 | for (int i = 0; i < text.length; i++) { 41 | String c = text[i]; 42 | 43 | if (whitespace.contains(c)) { 44 | // skip 45 | } else if (c == '/' && _peek(i) == '/') { 46 | int index = text.indexOf('\n', i); 47 | if (index == -1) index = text.length; 48 | _emit(text.substring(i, index)); 49 | i = index; 50 | } else if (alphaNum.contains(c)) { 51 | int start = i; 52 | 53 | while (alphaNum.contains(_peek(i))) { 54 | i++; 55 | } 56 | 57 | _emit(text.substring(start, i + 1)); 58 | } else { 59 | _emit(c); 60 | } 61 | } 62 | 63 | _emit(null); 64 | 65 | _head = _head.next; 66 | 67 | return _head; 68 | } 69 | 70 | void _emit(String value) { 71 | Token token = new Token(value); 72 | if (_head == null) _head = token; 73 | if (_last != null) _last.next = token; 74 | _last = token; 75 | } 76 | 77 | String _peek(int i) { 78 | i += 1; 79 | return i < text.length ? text[i] :new String.fromCharCodes([0]); 80 | } 81 | 82 | String toString() { 83 | StringBuffer buf = new StringBuffer(); 84 | 85 | Token t = _head; 86 | 87 | buf.write('[${t}]\n'); 88 | 89 | while (!t.eof) { 90 | t = t.next; 91 | buf.write('[${t}]\n'); 92 | } 93 | 94 | return buf.toString().trim(); 95 | } 96 | } 97 | 98 | abstract class Parser { 99 | final Token startToken; 100 | 101 | Token current; 102 | 103 | Parser(this.startToken); 104 | 105 | Token expect(String text) { 106 | Token t = advance(); 107 | if (text != t.text) fail('expected ${text}, got ${t}'); 108 | return t; 109 | } 110 | 111 | bool consume(String text) { 112 | if (peek().text == text) { 113 | advance(); 114 | return true; 115 | } else { 116 | return false; 117 | } 118 | } 119 | 120 | Token peek() => current.eof ? current : current.next; 121 | 122 | Token expectName() { 123 | Token t = advance(); 124 | if (!t.isName) fail('expected name token, got ${t}'); 125 | return t; 126 | } 127 | 128 | Token advance() { 129 | if (current == null) { 130 | current = startToken; 131 | } else if (!current.eof) { 132 | current = current.next; 133 | } 134 | 135 | return current; 136 | } 137 | 138 | String collectComments() { 139 | StringBuffer buf = new StringBuffer(); 140 | 141 | while (peek().isComment) { 142 | Token t = advance(); 143 | String str = t.text.substring(2); 144 | buf.write(' ${str}'); 145 | } 146 | 147 | if (buf.isEmpty) return null; 148 | return collapseWhitespace(buf.toString()).trim(); 149 | } 150 | 151 | void validate(bool result, String message) { 152 | if (!result) throw 'expected ${message}'; 153 | } 154 | 155 | void fail(String message) => throw message; 156 | } 157 | -------------------------------------------------------------------------------- /tool/src/src_gen.dart: -------------------------------------------------------------------------------- 1 | 2 | /// A library to generate Dart source code. 3 | library src_gen; 4 | 5 | const int RUNE_SPACE = 32; 6 | const int RUNE_EOL = 10; 7 | const int RUNE_LEFT_CURLY = 123; 8 | const int RUNE_RIGHT_CURLY = 125; 9 | 10 | final RegExp _wsRegexp = new RegExp(r'\s+'); 11 | 12 | String collapseWhitespace(String str) => str.replaceAll(_wsRegexp, ' '); 13 | 14 | /// foo ==> Foo 15 | String titleCase(String str) => 16 | str.substring(0, 1).toUpperCase() + str.substring(1); 17 | 18 | /// FOO ==> Foo 19 | String forceTitleCase(String str) { 20 | if (str == null || str.isEmpty) return str; 21 | return str.substring(0, 1).toUpperCase() + str.substring(1).toLowerCase(); 22 | } 23 | 24 | String joinLast(Iterable strs, String join, [String last]) { 25 | if (strs.isEmpty) return ''; 26 | List list = strs.toList(); 27 | if (list.length == 1) return list.first; 28 | StringBuffer buf = new StringBuffer(); 29 | for (int i = 0; i < list.length; i++) { 30 | if (i > 0) { 31 | if (i + 1 == list.length && last != null) { 32 | buf.write(last); 33 | } else { 34 | buf.write(join); 35 | } 36 | } 37 | buf.write(list[i]); 38 | } 39 | return buf.toString(); 40 | } 41 | 42 | /** 43 | * A class used to generate Dart source code. This class facilitates writing out 44 | * dartdoc comments, automatically manages indent by counting curly braces, and 45 | * automatically wraps doc comments on 80 char column boundaries. 46 | */ 47 | class DartGenerator { 48 | static const DEFAULT_COLUMN_BOUNDARY = 80; 49 | 50 | final int colBoundary; 51 | 52 | String _indent = ""; 53 | final StringBuffer _buf = new StringBuffer(); 54 | 55 | bool _previousWasEol = false; 56 | 57 | DartGenerator({this.colBoundary: DEFAULT_COLUMN_BOUNDARY}); 58 | 59 | /** 60 | * Write out the given dartdoc text, wrapping lines as necessary to flow 61 | * along the column boundary. If [preferSingle] is true, and the docs would 62 | * fit on a single line, use `///` dartdoc style. 63 | */ 64 | void writeDocs(String docs) { 65 | if (docs == null) return; 66 | 67 | docs = wrap(docs.trim(), colBoundary - _indent.length - 4); 68 | // docs = docs.replaceAll('*/', '/'); 69 | // docs = docs.replaceAll('/*', r'/\*'); 70 | 71 | docs.split('\n').forEach((line) => _writeln('/// ${line}')); 72 | 73 | // if (!docs.contains('\n') && preferSingle) { 74 | // _writeln("/// ${docs}", true); 75 | // } else { 76 | // _writeln("/**", true); 77 | // _writeln(" * ${docs.replaceAll("\n", "\n * ")}", true); 78 | // _writeln(" */", true); 79 | // } 80 | } 81 | 82 | /** 83 | * Write out the given Dart statement and terminate it with an eol. If the 84 | * statement will overflow the column boundary, attempt to wrap it at 85 | * reasonable places. 86 | */ 87 | void writeStatement(String str) { 88 | if (_indent.length + str.length > colBoundary) { 89 | // Split the line on the first '('. Currently, we don't do anything 90 | // fancier then that. This takes the edge off the long lines. 91 | int index = str.indexOf('('); 92 | 93 | if (index == -1) { 94 | writeln(str); 95 | } else { 96 | writeln(str.substring(0, index + 1)); 97 | writeln(" ${str.substring(index + 1)}"); 98 | } 99 | } else { 100 | writeln(str); 101 | } 102 | } 103 | 104 | void writeln([String str = ""]) => _write("${str}\n"); 105 | 106 | void write(String str) => _write(str); 107 | 108 | void out(String str) => _buf.write(str); 109 | 110 | void _writeln([String str = "", bool ignoreCurlies = false]) => 111 | _write("${str}\n", ignoreCurlies); 112 | 113 | void _write(String str, [bool ignoreCurlies = false]) { 114 | for (final int rune in str.runes) { 115 | if (!ignoreCurlies) { 116 | if (rune == RUNE_LEFT_CURLY) { 117 | _indent = "${_indent} "; 118 | } else if (rune == RUNE_RIGHT_CURLY && _indent.length >= 2) { 119 | _indent = _indent.substring(2); 120 | } 121 | } 122 | 123 | if (_previousWasEol && rune != RUNE_EOL) { 124 | _buf.write(_indent); 125 | } 126 | 127 | _buf.write(new String.fromCharCode(rune)); 128 | 129 | _previousWasEol = rune == RUNE_EOL; 130 | } 131 | } 132 | 133 | String toString() => _buf.toString(); 134 | } 135 | 136 | /// Wrap a string on column boundaries. 137 | String wrap(String str, [int col = 80]) { 138 | // The given string could contain newlines. 139 | List lines = str.split('\n'); 140 | return lines.map((l) => _simpleWrap(l, col)).join('\n'); 141 | } 142 | 143 | /// Wrap a string ignoring newlines. 144 | String _simpleWrap(String str, [int col = 80]) { 145 | List lines = []; 146 | 147 | while (str.length > col) { 148 | int index = col; 149 | 150 | while (index > 0 && str.codeUnitAt(index) != RUNE_SPACE) { 151 | index--; 152 | } 153 | 154 | if (index == 0) { 155 | index = str.indexOf(' '); 156 | 157 | if (index == -1) { 158 | lines.add(str); 159 | str = ''; 160 | } else { 161 | lines.add(str.substring(0, index).trim()); 162 | str = str.substring(index).trim(); 163 | } 164 | } else { 165 | lines.add(str.substring(0, index).trim()); 166 | str = str.substring(index).trim(); 167 | } 168 | } 169 | 170 | if (str.length > 0) lines.add(str); 171 | 172 | return lines.join('\n'); 173 | } 174 | -------------------------------------------------------------------------------- /tool/test.dart: -------------------------------------------------------------------------------- 1 | library foo_test; 2 | 3 | import 'dart:async'; 4 | import 'dart:convert' show JSON; 5 | import 'dart:developer' as dev; 6 | import 'dart:io'; 7 | import 'dart:isolate'; 8 | import 'dart:typed_data'; 9 | 10 | void main(List args) { 11 | print('args: ${args}'); 12 | print(Directory.current); 13 | 14 | String abc = 'abd_def'; 15 | String longText = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, ' 16 | 'sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut ' 17 | 'enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut ' 18 | 'aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit ' 19 | 'in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur ' 20 | 'sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt ' 21 | 'mollit anim id est laborum.'; 22 | int count = longText.length; 23 | 24 | Cat pebbles; 25 | Dog fido = new Dog(Dog.FIDO_NAME, parent: new Dog('Sam')); 26 | 27 | Map pets = { 28 | 'pebbles': pebbles, 29 | fido.name: fido, 30 | 'type': fido.runtimeType 31 | }; 32 | 33 | Timer.run(() => print('timer 1')); 34 | Timer.run(_handleTimer); 35 | 36 | // dev.registerExtension('foo', fooHandler); 37 | // 38 | // dev.log('log from test'); 39 | 40 | // dev.Timeline.timeSync('frame', _mockFrame); 41 | // dev.inspect(fido); 42 | 43 | // int i = 0; 44 | // 45 | // new Timer.periodic(new Duration(milliseconds: 10), (t) { 46 | // print('foo ${i}'); 47 | // i++; 48 | // if (i > 300) t.cancel(); 49 | // }); 50 | 51 | print('foo 1'); 52 | print('foo 2'); 53 | print('foo 3'); 54 | 55 | var typedList = new Int32List.fromList([1, 2, 3, 23476234]); 56 | 57 | dev.debugger(); 58 | 59 | print('calcRecursive: ${calcRecursive(300)}'); 60 | 61 | // startIsolates(4); 62 | 63 | // dev.log('log from test', name: 'test', level: 1); 64 | // dev.Timeline.timeSync('frame', _mockFrame); 65 | // dev.Timeline.timeSync('frame', _mockFrame); 66 | 67 | print('${abc} ${count}, ${pets.length}, ${typedList.length}'); 68 | 69 | pebbles = new Cat('Pebbles'); 70 | 71 | List animals = [ 72 | pebbles, fido, pebbles, fido, pebbles, fido, pebbles, fido, pebbles, fido 73 | ]; 74 | 75 | print(pebbles); 76 | print(fido); 77 | 78 | // if (pebbles.scratches()) { 79 | // throw 'no scratching'; 80 | // } 81 | 82 | print(animals); 83 | 84 | fido.bark(); 85 | 86 | // Demonstrates a game with 3 discs on pegs labeled '1', '2' and '3'. 87 | hanoi(4, '1', '2', '3'); 88 | } 89 | 90 | abstract class Animal { 91 | final String name; 92 | final Animal parent; 93 | 94 | Animal(this.name, {this.parent}); 95 | 96 | String toString() => '[${runtimeType} ${name}]'; 97 | } 98 | 99 | class Cat extends Animal { 100 | Cat(String name) : super(name); 101 | 102 | bool scratches() => true; 103 | } 104 | 105 | class Dog extends Animal { 106 | static String FIDO_NAME = 'Fido'; 107 | 108 | Dog(String name, {Dog parent}) : super(name, parent: parent); 109 | 110 | void bark() { 111 | print('woof!'); 112 | } 113 | } 114 | 115 | String say(String from, String to) => "move $from -> $to"; 116 | 117 | // Makes a move and recursively triggers next moves, if any. 118 | void hanoi(int discs, String a, String b, String c) { 119 | // Makes a move only if there are discs. 120 | if (discs > 0) { 121 | // if (discs == 1 && a == '1') dev.debugger(); 122 | 123 | // Announces this move, from A to C. 124 | print('[${discs}] ${say(a, c)}'); 125 | 126 | // Triggers the next step: from A to B. 127 | hanoi(discs - 1, a, c, b); 128 | 129 | // Triggers the last step: from B to C. 130 | hanoi(discs - 1, b, a, c); 131 | } 132 | } 133 | 134 | // dynamic _mockFrame() { 135 | // final List names = [ 136 | // 'Fido', 'Sparky', 'Chips', 'Scooter' 137 | // ]; 138 | // 139 | // return names.map((name) => new Dog(name)).toList(); 140 | // } 141 | 142 | void _handleTimer() { 143 | print('timer 2'); 144 | } 145 | 146 | Future fooHandler(String method, Map parameters) { 147 | String result = JSON.encode({ 148 | 'type': '_extensionType', 149 | 'method': method, 150 | 'parameters': parameters, 151 | }); 152 | return new Future.value(new dev.ServiceExtensionResponse.result(result)); 153 | } 154 | 155 | void startIsolates(int count) { 156 | if (count == 0) return; 157 | 158 | startIsolate(count * 4); 159 | 160 | startIsolates(count - 1); 161 | startIsolates(count - 1); 162 | } 163 | 164 | Future startIsolate(int seconds) { 165 | return Isolate.spawn(isolateEntryPoint, seconds); 166 | } 167 | 168 | void isolateEntryPoint(seconds) { 169 | print('[${Isolate.current}] starting'); 170 | print('[${Isolate.current}] running for ${seconds} seconds...'); 171 | new Timer(new Duration(seconds: seconds), () { 172 | print('[${Isolate.current}] exiting'); 173 | }); 174 | } 175 | 176 | int calcRecursive(int depth) { 177 | if (depth == 0) { 178 | return 1; 179 | } else { 180 | return depth + calcRecursive(depth - 1); 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /tool/travis.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 4 | # for details. All rights reserved. Use of this source code is governed by a 5 | # BSD-style license that can be found in the LICENSE file. 6 | 7 | # Fast fail the script on failures. 8 | set -e 9 | 10 | # Analyze, build and test. 11 | # TODO: Re-enable the CI. 12 | # Disable analysis and tests for now, until the codebase works under Dart 2.0. 13 | #pub run grinder bot 14 | -------------------------------------------------------------------------------- /web/entry.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library atom.entry; 6 | 7 | import 'package:atom/node/package.dart'; 8 | import 'package:atom_dart/plugin.dart'; 9 | import 'package:logging/logging.dart'; 10 | 11 | main() { 12 | Logger.root.level = Level.WARNING; 13 | Logger.root.onRecord.listen((LogRecord r) { 14 | String tag = '${r.level.name.toLowerCase()} • ${r.loggerName}:'; 15 | print('${tag} ${r.message}'); 16 | 17 | if (r.error != null) print('${tag} ${r.error}'); 18 | if (r.stackTrace != null) print('${tag} ${r.stackTrace}'); 19 | }); 20 | 21 | registerPackage(new AtomDartPackage()); 22 | } 23 | --------------------------------------------------------------------------------