├── .gitignore ├── CHANGELOG.MD ├── README.MD ├── images ├── code-hint.png └── quick-edit.png ├── main.js ├── package.json ├── src ├── CodeMirror │ └── rust.js ├── LintProvider.js ├── QuickOpenPlugin.js ├── RacerCli.js ├── RacerProviders.js ├── RustUtils.js ├── SyntaxColoring.js ├── dialogs │ ├── RacerSettings.js │ └── templates │ │ └── rust-ide-settings.html └── node │ ├── LintDomain.js │ └── RacerDomain.js └── styles └── main.css /.gitignore: -------------------------------------------------------------------------------- 1 | # https://git-scm.com/docs/gitignore 2 | # https://help.github.com/articles/ignoring-files 3 | # Example .gitignore files: https://github.com/github/gitignore 4 | /bower_components/ 5 | /node_modules/ 6 | 7 | *.zip 8 | tmp* 9 | hello.rs -------------------------------------------------------------------------------- /CHANGELOG.MD: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## 1.0.8 - 2015-11-28 4 | - add styles for different types of hint 5 | - rewrite `HintProvider` to let hints arise immediately 6 | - update rust.js to resolve inserting string bug. 7 | 8 | ## 1.0.7 - 2015-11-21 9 | - new feature: lint 10 | - delete own rust.js and simple.js since new version of Brackets includes them 11 | 12 | ## 1.0.6 - 2015-8-23 13 | - new feature: quick-edit 14 | 15 | ## 1.0.5 - 2015-8-18 16 | - Fix [issue #2](https://github.com/rrandom/Brackets-Rust-IDE/issues/2) 17 | 18 | ## 1.0.4 - 2015-8-12 19 | - Update hint coloring scheme. 20 | - Update rust hightlighting scheme from CodeMirror. 21 | - Some small issues 22 | 23 | ## 1.0.3 - 2015-7-23 24 | - Rust std macros hints. 25 | - New CodeMirror mode rust.js for highlighting. 26 | 27 | ## 1.0.2 - 2015-7-14 28 | - Highlighting toml. 29 | - Rust keywords hints. 30 | - CSS style for hints. 31 | - Call racer more efficiently. 32 | 33 | ## 1.0.1 - 2015-6-28 34 | - racer integration. 35 | 36 | ## 1.0.0 - 2015-6-14 37 | 38 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # Rust-IDE 2 | #### Brackets extension Rust-IDE 3 | ---- 4 | 5 | This Brackets extension needs racer to complete code. After install racer correctly, open the Rust-IDE Settings dialog from File Menu, set the correct racer path. 6 | 7 | Get ``racer`` here: 8 | https://github.com/phildawes/racer 9 | 10 | #### Features 11 | 12 | ##### Lint (experimental) 13 | Lint use `rustc -Zno-trans` and `cargo rustc -Zno-trans`, so ensure you have `rustc` and `cargo` in your system path. 14 | 15 | ##### Code Hint 16 | ![Rust-IDE](https://raw.githubusercontent.com/rrandom/Brackets-Rust-IDE/master/images/code-hint.png) 17 | 18 | ##### Quick Edit 19 | ![Rust-IDE](https://raw.githubusercontent.com/rrandom/Brackets-Rust-IDE/master/images/quick-edit.png) 20 | 21 | 22 | 23 | #### NOTE 24 | quick-edit now search function endline based on your rust code indentation, the result maybe inaccurate. 25 | 26 | 27 | #### Credits 28 | - [David5i6](https://github.com/David5i6/Brackets-Go-IDE) 29 | - [mackenza](https://github.com/mackenza/Brackets-PHP-SmartHints) 30 | - [JSUtils](https://github.com/adobe/brackets/blob/5ef84133cb8c5acdcb7e80b85bbee86f65c2c9b1/src/language/JSUtils.js) 31 | - [phoenix3008](https://github.com/phoenix3008/brackets-phplinter) -------------------------------------------------------------------------------- /images/code-hint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrandom/Brackets-Rust-IDE/1fda6e266c066743ff39c47ebc8a138a367bd5ed/images/code-hint.png -------------------------------------------------------------------------------- /images/quick-edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrandom/Brackets-Rust-IDE/1fda6e266c066743ff39c47ebc8a138a367bd5ed/images/quick-edit.png -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012 Adobe Systems Incorporated. All rights reserved. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | * 22 | */ 23 | 24 | /*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */ 25 | /*global define, brackets */ 26 | 27 | define(function (require, exports, module) { 28 | "use strict"; 29 | 30 | var AppInit = brackets.getModule("utils/AppInit"); 31 | 32 | var CodeHintManager = brackets.getModule("editor/CodeHintManager"), 33 | EditorManager = brackets.getModule("editor/EditorManager"), 34 | ExtensionUtils = brackets.getModule("utils/ExtensionUtils"), 35 | QuickOpen = brackets.getModule("search/QuickOpen"), 36 | LanguageManager = brackets.getModule("language/LanguageManager"); 37 | 38 | var RacerSettings = require("src/dialogs/RacerSettings"), 39 | RacerProviders = require("src/RacerProviders"), 40 | QuickOpenPlugin = require("src/QuickOpenPlugin"), 41 | SyntaxColoring = require("src/SyntaxColoring"), 42 | LintProvider = require("src/LintProvider"); 43 | 44 | function startup() { 45 | try { 46 | QuickOpen.addQuickOpenPlugin({ 47 | name: "Rust functions", 48 | languageIds: ["rust"], 49 | search: QuickOpenPlugin.search, 50 | match: QuickOpenPlugin.match, 51 | itemFocus: QuickOpenPlugin.itemFocus, 52 | itemSelect: QuickOpenPlugin.itemSelect 53 | }); 54 | 55 | LanguageManager.defineLanguage("rust", { 56 | name: "Rust", 57 | mode: ["rust", "text/x-rustsrc"], 58 | fileExtensions: ["rs"], 59 | blockComment: ["/*", "*/"], 60 | lineComment: ["//"] 61 | }); 62 | 63 | LanguageManager.defineLanguage("toml", { 64 | name: "toml", 65 | mode: ["toml", "text/x-toml"], 66 | fileExtensions: ["toml"], 67 | lineComment: ["#"] 68 | }); 69 | 70 | ExtensionUtils.loadStyleSheet(module, "styles/main.css"); 71 | console.info("Registering Rust Providers"); 72 | 73 | var rustHintProvider = new RacerProviders.RustHintProvider(), 74 | rustDefinitionProvider = new RacerProviders.RustDefinitionProvider(); 75 | 76 | CodeHintManager.registerHintProvider(rustHintProvider, ["rust"], 10); 77 | EditorManager.registerInlineEditProvider(rustDefinitionProvider.provider); 78 | 79 | LintProvider.init(); 80 | 81 | console.info("Registered Rust Providers"); 82 | } catch (e) { 83 | console.error("Error starting up Rust providers", e); 84 | setTimeout(startup, 10000); 85 | } 86 | } 87 | 88 | AppInit.appReady(function () { 89 | 90 | startup(); 91 | 92 | }); 93 | 94 | }); 95 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rrandom.rust-ide", 3 | "title": "Rust IDE", 4 | "description": "Help coding Rust in Brackets: lint, autocomplete, quick-edit, syntax highlight", 5 | "homepage": "https://github.com/rrandom/Brackets-Rust-IDE", 6 | "version": "1.0.8", 7 | "author": "Rrandom (https://github.com/rrandom)", 8 | "license": "MIT", 9 | "categories": "language", 10 | "engines": { 11 | "brackets": ">=1.5.0" 12 | }, 13 | "keywords": ["Rust", "IDE", "language"] 14 | } 15 | -------------------------------------------------------------------------------- /src/CodeMirror/rust.js: -------------------------------------------------------------------------------- 1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others 2 | // Distributed under an MIT license: http://codemirror.net/LICENSE 3 | 4 | /* 5 | (function(mod) { 6 | if (typeof exports == "object" && typeof module == "object") // CommonJS 7 | mod(require("../../lib/codemirror"), require("../../addon/mode/simple")); 8 | else if (typeof define == "function" && define.amd) // AMD 9 | define(["../../lib/codemirror", "../../addon/mode/simple"], mod); 10 | else // Plain browser env 11 | mod(CodeMirror); 12 | })(function(CodeMirror) { 13 | "use strict"; 14 | */ 15 | 16 | define(function (require, exports, module){ 17 | "use strict"; 18 | 19 | 20 | 21 | CodeMirror.defineSimpleMode("rust",{ 22 | start: [ 23 | // string and byte string 24 | {regex: /b?"/, token: "string", next: "string"}, 25 | // raw string and raw byte string 26 | {regex: /b?r"/, token: "string", next: "string_raw"}, 27 | {regex: /b?r#+"/, token: "string", next: "string_raw_hash"}, 28 | // character 29 | {regex: /'(?:[^'\\]|\\(?:[nrt0'"]|x[\da-fA-F]{2}|u\{[\da-fA-F]{6}\}))'/, token: "string-2"}, 30 | // byte 31 | {regex: /b'(?:[^']|\\(?:['\\nrt0]|x[\da-fA-F]{2}))'/, token: "string-2"}, 32 | 33 | {regex: /(?:(?:[0-9][0-9_]*)(?:(?:[Ee][+-]?[0-9_]+)|\.[0-9_]+(?:[Ee][+-]?[0-9_]+)?)(?:f32|f64)?)|(?:0(?:b[01_]+|(?:o[0-7_]+)|(?:x[0-9a-fA-F_]+))|(?:[0-9][0-9_]*))(?:u8|u16|u32|u64|i8|i16|i32|i64|isize|usize)?/, 34 | token: "number"}, 35 | {regex: /(let(?:\s+mut)?|fn|enum|mod|struct|type)(\s+)([a-zA-Z_][a-zA-Z0-9_]*)/, token: ["keyword", null, "def"]}, 36 | {regex: /(?:abstract|alignof|as|box|break|continue|const|crate|do|else|enum|extern|fn|for|final|if|impl|in|loop|macro|match|mod|move|offsetof|override|priv|proc|pub|pure|ref|return|self|sizeof|static|struct|super|trait|type|typeof|unsafe|unsized|use|virtual|where|while|yield)\b/, token: "keyword"}, 37 | {regex: /\b(?:Self|isize|usize|char|bool|u8|u16|u32|u64|f16|f32|f64|i8|i16|i32|i64|str|Option)\b/, token: "atom"}, 38 | {regex: /\b(?:true|false|Some|None|Ok|Err)\b/, token: "builtin"}, 39 | {regex: /\b(fn)(\s+)([a-zA-Z_][a-zA-Z0-9_]*)/, 40 | token: ["keyword", null ,"def"]}, 41 | {regex: /#!?\[.*\]/, token: "meta"}, 42 | {regex: /\/\/.*/, token: "comment"}, 43 | {regex: /\/\*/, token: "comment", next: "comment"}, 44 | {regex: /[-+\/*=<>!]+/, token: "operator"}, 45 | {regex: /[a-zA-Z_]\w*!/,token: "variable-3"}, 46 | {regex: /[a-zA-Z_]\w*/, token: "variable"}, 47 | {regex: /[\{\[\(]/, indent: true}, 48 | {regex: /[\}\]\)]/, dedent: true} 49 | ], 50 | string: [ 51 | {regex: /"/, token: "string", next: "start"}, 52 | {regex: /(?:[^\\"]|\\(?:.|$))*/, token: "string"} 53 | ], 54 | string_raw: [ 55 | {regex: /"/, token: "string", next: "start"}, 56 | {regex: /[^"]*/, token: "string"} 57 | ], 58 | string_raw_hash: [ 59 | {regex: /"#+/, token: "string", next: "start"}, 60 | {regex: /(?:[^"]|"(?!#))*/, token: "string"} 61 | ], 62 | comment: [ 63 | {regex: /.*?\*\//, token: "comment", next: "start"}, 64 | {regex: /.*/, token: "comment"} 65 | ], 66 | meta: { 67 | dontIndentStates: ["comment"], 68 | electricInput: /^\s*\}$/, 69 | blockCommentStart: "/*", 70 | blockCommentEnd: "*/", 71 | lineComment: "//", 72 | fold: "brace" 73 | } 74 | }); 75 | 76 | 77 | CodeMirror.defineMIME("text/x-rustsrc", "rust"); 78 | }); 79 | -------------------------------------------------------------------------------- /src/LintProvider.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Licensed under MIT 4 | * 5 | */ 6 | 7 | /*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */ 8 | /*global define, $, brackets */ 9 | 10 | 11 | define(function (require, exports, module) { 12 | "use strict"; 13 | 14 | // TO-DO: inlineWidget 15 | 16 | var DocumentManager = brackets.getModule("document/DocumentManager"), 17 | CodeInspection = brackets.getModule("language/CodeInspection"), 18 | EditorManager = brackets.getModule("editor/EditorManager"), 19 | NodeDomain = brackets.getModule("utils/NodeDomain"), 20 | ExtensionUtils = brackets.getModule("utils/ExtensionUtils"), 21 | ProjectManager = brackets.getModule("project/ProjectManager"); 22 | 23 | var pattern = /^(.+?):(\d+):(\d+):\s+(\d+):(\d+)\s(error|fatal error|warning):\s+(.+)/; 24 | 25 | var _domainPath = ExtensionUtils.getModulePath(module, "node/LintDomain"); 26 | 27 | var _nodeDomain = new NodeDomain("rustLint", _domainPath); 28 | 29 | // TO-DO: `--lib or --bin NAME` 30 | function getLintErrors(filePath, useCargo, manifest) { 31 | var errors, 32 | deferred = new $.Deferred(), 33 | cmd = useCargo ? "cargo rustc -Zno-trans --manifest-path " + manifest : "rustc -Z no-trans " + filePath; 34 | 35 | _nodeDomain.exec("getLint", cmd) 36 | .done(function (data) { 37 | console.log("[RustLint]:\n", data); 38 | errors = parserError(data, filePath); 39 | deferred.resolve(errors); 40 | }).fail(function (err) { 41 | console.error("RustLint: ", err); 42 | deferred.reject(null); 43 | }); 44 | 45 | return deferred.promise(); 46 | } 47 | 48 | function normalizePath(path) { 49 | return path.replace(/[\/\\]/g, "/"); 50 | } 51 | 52 | function parserError(data, lintFile) { 53 | return data.split(/(?:\r\n|\r|\n)/g) 54 | .map(function (message) { 55 | var match = pattern.exec(message); 56 | if (match) { 57 | return { 58 | file: normalizePath(match[1]), 59 | pos: { 60 | line: match[2] - 1, 61 | ch: match[3] - 1 62 | }, 63 | endPos: { 64 | line: match[4] - 1, 65 | ch: +match[5] 66 | }, 67 | type: match[6] === "warning" ? "warning" : "error", 68 | message: match[7] 69 | }; 70 | } 71 | }).filter(function (error) { 72 | return (error !== undefined) && (error.file === lintFile); 73 | }); 74 | } 75 | 76 | // ------ gutter --- 77 | 78 | var lineMarkers = []; 79 | 80 | function registerGutter() { 81 | var currentEditor = EditorManager.getActiveEditor(), 82 | cm = currentEditor._codeMirror, 83 | gutters = cm.getOption("gutters").slice(0); 84 | if (gutters.indexOf("rust-linter-gutter") === -1) { 85 | gutters.unshift("rust-linter-gutter"); 86 | cm.setOption("gutters", gutters); 87 | } 88 | return cm; 89 | } 90 | 91 | function makeGutterMarker(type) { 92 | var lintType = (type === "error") ? "rust-linter-gutter-error" : "rust-linter-gutter-warning", 93 | marker = $("
"); 94 | marker.addClass(lintType); 95 | return marker[0]; 96 | } 97 | 98 | function addMarkers(cm, errors) { 99 | for (var i = 0; i < errors.length; i++) { 100 | var marker = makeGutterMarker(errors[i].type); 101 | lineMarkers.push(cm.markText(errors[i].pos, errors[i].endPos,{className: 'rust-linter-line-' + errors[i].type})); 102 | cm.setGutterMarker(errors[i].pos.line, "rust-linter-gutter", marker); 103 | } 104 | } 105 | 106 | function updateMarkers(errors, cm) { 107 | cm.clearGutter("rust-linter-gutter"); 108 | lineMarkers.forEach(function(lineMarker){ 109 | lineMarker.clear(); 110 | }); 111 | 112 | lineMarkers = []; 113 | 114 | if (errors.length > 0) { 115 | addMarkers(cm, errors); 116 | } 117 | } 118 | 119 | // ---- end gutter ---- 120 | 121 | // ---- inlineWidget ---- 122 | 123 | function toggleLineDetail(line){ 124 | 125 | } 126 | 127 | function hideLindeDetail(widget){ 128 | 129 | } 130 | 131 | function showLineDetail(line){ 132 | 133 | } 134 | 135 | function getWidgetForLine(line){ 136 | 137 | } 138 | 139 | // ---- end inlineWidget ---- 140 | 141 | 142 | var useCargo, 143 | codeMirror, 144 | manifestPath, 145 | isCrate = null; 146 | 147 | function fileInDirectory(filePath, directoryPath) { 148 | return filePath.indexOf(directoryPath) === 0; 149 | } 150 | 151 | // return a promise resolved with manifest-path if current project is a crate(with `Cargo.toml`) 152 | function locateManifest() { 153 | var names, ti, 154 | deferred = new $.Deferred(); 155 | 156 | ProjectManager.getAllFiles(ProjectManager.getLanguageFilter("toml")).done(function (files) { 157 | names = files.map(function (file) { 158 | return file._name; 159 | }); 160 | ti = names.indexOf("Cargo.toml"); 161 | if (ti > -1) { 162 | deferred.resolve({ 163 | isCrate: true, 164 | manifestPath: files[ti]._path 165 | }); 166 | } else { 167 | deferred.resolve({ 168 | isCrate: false, 169 | manifestPath: null 170 | }); 171 | } 172 | }); 173 | return deferred.promise(); 174 | } 175 | 176 | function activeEditorChangeHandler() { 177 | var currentProject, currentFilePath, 178 | currentDocument = DocumentManager.getCurrentDocument(); 179 | if (currentDocument) { 180 | if (currentDocument.language._name === "Rust") { 181 | currentFilePath = currentDocument.file._path; 182 | codeMirror = registerGutter(); 183 | currentProject = ProjectManager.getProjectRoot(); 184 | if (isCrate === null) { 185 | locateManifest().done(function (result) { 186 | isCrate = result.isCrate; 187 | manifestPath = result.manifestPath; 188 | 189 | useCargo = isCrate && fileInDirectory(currentFilePath, currentProject._path); 190 | CodeInspection.requestRun(); 191 | }); 192 | } else { 193 | useCargo = isCrate && fileInDirectory(currentFilePath, currentProject._path); 194 | CodeInspection.requestRun(); 195 | } 196 | } 197 | } 198 | 199 | } 200 | 201 | function projectOpenHandler() { 202 | isCrate = null; 203 | } 204 | 205 | function linter(text, fullPath) { 206 | var deferred = new $.Deferred(); 207 | getLintErrors(fullPath, useCargo, manifestPath).done(function (errors) { 208 | updateMarkers(errors, codeMirror); 209 | deferred.resolve({ 210 | errors: errors 211 | }); 212 | }); 213 | 214 | return deferred.promise(); 215 | } 216 | 217 | function init() { 218 | CodeInspection.register("rust", { 219 | name: "rustLint", 220 | scanFileAsync: linter 221 | }); 222 | 223 | ProjectManager.on("projectOpen", projectOpenHandler); 224 | EditorManager.on("activeEditorChange", activeEditorChangeHandler); 225 | } 226 | 227 | exports.init = init; 228 | }); 229 | -------------------------------------------------------------------------------- /src/QuickOpenPlugin.js: -------------------------------------------------------------------------------- 1 | /*jslint vars: true, plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50, browser: true */ 2 | /*global define, $, brackets */ 3 | 4 | define(function (require, exports, module) { 5 | "use strict"; 6 | var EditorManager = brackets.getModule("editor/EditorManager"), 7 | DocumentManager = brackets.getModule("document/DocumentManager"), 8 | StringMatch = brackets.getModule("utils/StringMatch"); 9 | 10 | var RustUtils = require("src/RustUtils"); 11 | 12 | 13 | /** 14 | * FileLocation class 15 | * @constructor 16 | * @param {string} fullPath 17 | * @param {number} line 18 | * @param {number} chFrom column start position 19 | * @param {number} chTo column end position 20 | * @param {string} functionName 21 | */ 22 | function FileLocation(fullPath, line, chFrom, chTo, functionName) { 23 | this.fullPath = fullPath; 24 | this.line = line; 25 | this.chFrom = chFrom; 26 | this.chTo = chTo; 27 | this.functionName = functionName; 28 | } 29 | 30 | 31 | /** 32 | * Contains a list of information about functions for a single document. 33 | * 34 | * @return {?Array.} 35 | */ 36 | function createFunctionList() { 37 | var doc = DocumentManager.getCurrentDocument(); 38 | if (!doc) { 39 | return; 40 | } 41 | 42 | var functionList = []; 43 | var docText = doc.getText(); 44 | var lines = docText.split("\n"); 45 | var functions = RustUtils.findAllMatchingFunctionsInText(docText, "*"); 46 | functions.forEach(function (funcEntry) { 47 | var chFrom = lines[funcEntry.lineStart].indexOf(funcEntry.name); 48 | var chTo = chFrom + funcEntry.name.length; 49 | functionList.push(new FileLocation(null, funcEntry.lineStart, chFrom, chTo, funcEntry.name)); 50 | }); 51 | return functionList; 52 | } 53 | 54 | /** 55 | * @param {string} query what the user is searching for 56 | * @param {StringMatch.StringMatcher} matcher object that caches search-in-progress data 57 | * @return {Array.} sorted and filtered results that match the query 58 | */ 59 | function search(query, matcher) { 60 | var functionList = matcher.functionList; 61 | if (!functionList) { 62 | functionList = createFunctionList(); 63 | matcher.functionList = functionList; 64 | } 65 | query = query.slice(query.indexOf("@") + 1, query.length); 66 | 67 | // Filter and rank how good each match is 68 | var filteredList = $.map(functionList, function (fileLocation) { 69 | var searchResult = matcher.match(fileLocation.functionName, query); 70 | if (searchResult) { 71 | searchResult.fileLocation = fileLocation; 72 | } 73 | return searchResult; 74 | }); 75 | 76 | // Sort based on ranking & basic alphabetical order 77 | StringMatch.basicMatchSort(filteredList); 78 | 79 | return filteredList; 80 | } 81 | 82 | /** 83 | * @param {string} query what the user is searching for 84 | * @param {boolean} returns true if this plug-in wants to provide results for this query 85 | */ 86 | function match(query) { 87 | // only match @ at beginning of query for now 88 | return (query[0] === "@"); 89 | } 90 | 91 | /** 92 | * Scroll to the selected item in the current document (unless no query string entered yet, 93 | * in which case the topmost list item is irrelevant) 94 | * @param {?SearchResult} selectedItem 95 | * @param {string} query 96 | * @param {boolean} explicit False if this is only highlighted due to being at top of list after search() 97 | */ 98 | function itemFocus(selectedItem, query, explicit) { 99 | if (!selectedItem || (query.length < 2 && !explicit)) { 100 | return; 101 | } 102 | var fileLocation = selectedItem.fileLocation; 103 | 104 | var from = { 105 | line: fileLocation.line, 106 | ch: fileLocation.chFrom 107 | }; 108 | var to = { 109 | line: fileLocation.line, 110 | ch: fileLocation.chTo 111 | }; 112 | EditorManager.getCurrentFullEditor().setSelection(from, to, true); 113 | 114 | } 115 | 116 | function itemSelect(selectedItem, query) { 117 | itemFocus(selectedItem, query, true); 118 | 119 | } 120 | 121 | 122 | exports.search = search; 123 | exports.match = match; 124 | exports.itemFocus = itemFocus; 125 | exports.itemSelect = itemSelect; 126 | }); 127 | -------------------------------------------------------------------------------- /src/RacerCli.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Licensed under MIT 4 | * 5 | * 6 | */ 7 | 8 | /*jslint vars: true, plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50, browser: true */ 9 | /*global define, $, brackets */ 10 | 11 | 12 | define(function (require, exports, module) { 13 | "use strict"; 14 | 15 | var ExtensionUtils = brackets.getModule("utils/ExtensionUtils"), 16 | NodeDomain = brackets.getModule("utils/NodeDomain"), 17 | PreferencesManager = brackets.getModule("preferences/PreferencesManager"); 18 | 19 | var RacerDomain, 20 | extPath = ExtensionUtils.getModulePath(module), 21 | prefs = PreferencesManager.getExtensionPrefs("Rust-IDE"); 22 | 23 | 24 | RacerDomain = new NodeDomain("RacerDomain", 25 | ExtensionUtils.getModulePath(module, 26 | "node/RacerDomain")); 27 | 28 | 29 | function getHintsD(txt, pos, vpet) { 30 | var args = { 31 | txt: txt, 32 | line: pos.line + 1, 33 | char: pos.ch, 34 | path: extPath, 35 | isPathTmp: true 36 | }; 37 | var $deferred = new $.Deferred(); 38 | RacerDomain.exec("getHint", prefs.get("racerPath"), args, vpet).done(function (data) { 39 | $deferred.resolve(data); 40 | }).fail(function (err) { 41 | console.error("[RacerDomain] Fail to get hints: ", err); 42 | }); 43 | return $deferred.promise(); 44 | } 45 | 46 | function getDefD(txt, pos, vpet, path) { 47 | var args = { 48 | txt: txt, 49 | line: pos.line, 50 | char: pos.ch, 51 | path: path, 52 | isPathTmp: false 53 | }; 54 | var $deferred = new $.Deferred(); 55 | RacerDomain.exec("findDef", prefs.get("racerPath"), args, vpet).done(function (data) { 56 | $deferred.resolve(data); 57 | }).fail(function (err) { 58 | console.error("[RacerDomain] Fail to get Def: ", err); 59 | }); 60 | return $deferred.promise(); 61 | } 62 | 63 | 64 | function parse(str) { 65 | var result; 66 | try { 67 | var tmp = str.split(","); 68 | result = { 69 | str: tmp[0].split(" ")[1], 70 | line: tmp[1], 71 | char: tmp[2], 72 | path: tmp[3], 73 | type: tmp[4], 74 | firstLine: tmp[5] 75 | }; 76 | } catch (e) { 77 | console.error("[RacerDomain] Error when parse: ", e); 78 | } 79 | return result; 80 | 81 | } 82 | 83 | exports.getHintsD = getHintsD; 84 | exports.getDefD = getDefD; 85 | exports.parse = parse; 86 | }); 87 | -------------------------------------------------------------------------------- /src/RacerProviders.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Licensed under MIT 4 | * 5 | * 6 | */ 7 | 8 | 9 | /*jslint vars: true, plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50, browser: true */ 10 | /*global define, $, brackets */ 11 | 12 | define(function (require, exports, module) { 13 | "use strict"; 14 | 15 | var MultiRangeInlineEditor = brackets.getModule("editor/MultiRangeInlineEditor").MultiRangeInlineEditor, 16 | DocumentManager = brackets.getModule("document/DocumentManager"), 17 | FileSystem = brackets.getModule("filesystem/FileSystem"), 18 | StringUtils = brackets.getModule("utils/StringUtils"), 19 | CodeMirror = brackets.getModule("thirdparty/CodeMirror/lib/codemirror"), 20 | _ = brackets.getModule("thirdparty/lodash"); 21 | 22 | var RacerCli = require("src/RacerCli"); 23 | 24 | var vpet = 0; 25 | 26 | // TO-DO: css style for different hint type 27 | function RustHintProvider() { 28 | 29 | var prefix, _cm, lastToken, 30 | needNewHints = true, 31 | cachedHints = null, 32 | preTokenStr = "--dummy--", 33 | 34 | // keywords: https://doc.rust-lang.org/grammar.html#keywords 35 | rustKeywords = ["abstract", "alignof", "as", "become", "box", "break", 36 | "const", "continue", "crate", "do", "else", "enum", 37 | "extern", "false", "final", "fn", "for", "if", "impl", 38 | "in", "let", "loop", "macro", "match", "mod", "move", 39 | "mut", "offsetof", "override", "priv", "proc", "pub", 40 | "pure", "ref", "return", "Self", "self", "sizeof", "static", 41 | "struct", "super", "trait", "true", "type", "typeof", "unsafe", 42 | "unsized", "use", "virtual", "where", "while", "yield"], 43 | 44 | // std library macros: https://doc.rust-lang.org/nightly/std/index.html#macros 45 | stdMacros = ["assert!", "assert_eq!", "cfg!", "column!", "concat!", 46 | "contat_idents!", "debug_assert!", "debug_assert_eq!", 47 | "env!", "file!", "format!", "format_args!", "include!", 48 | "include_bytes!", "include_str!", "line!", "module_path!", 49 | "option_env!", "panic!", "print!", "println!", "scoped_thread_local!", 50 | "select!", "stringify!", "thread_local!", "try!", "unimplemented!", 51 | "unreachable!", "vec!", "write!", "writeln!"], 52 | 53 | endTokens = [" ", "+", "-", "/", "*", "(", ")", "[", "]", ",", "<", ">", ".", "{", "}"]; 54 | 55 | var auxiliaryHints = rustKeywords.map(function (s) { 56 | return { 57 | str: s, 58 | type: "Keyword" 59 | }; 60 | }).concat(stdMacros.map(function (s) { 61 | return { 62 | str: s, 63 | type: "Macro" 64 | }; 65 | })); 66 | 67 | // Racer output -> parsed hintsList 68 | function extractHints(data) { 69 | var rs = [], 70 | ta = data.split(/(?:\r\n|\r|\n)/g); 71 | prefix = ta.shift().split(",").pop(); 72 | ta.pop(); // "\n" 73 | try { 74 | ta.pop(); // "END" 75 | rs = ta.map(function (i) { 76 | return RacerCli.parse(i); 77 | }); 78 | } catch (e) { 79 | console.error("[RustHintProvider] extractHints: Please notify me if you see this error"); 80 | console.error("error:", e); 81 | } 82 | return _.uniq(rs); 83 | } 84 | 85 | function _validToken(implicitChar) { 86 | if (implicitChar) { 87 | var code = implicitChar.charCodeAt(0); 88 | return (endTokens.indexOf(implicitChar) === -1) && (code !== 13) && (code !== 9); 89 | } else { 90 | return false; 91 | } 92 | } 93 | 94 | // parsed hintsList from racer, cm token -> brackets hint object 95 | function buildHints(parsedHints, token) { 96 | prefix = token.string; 97 | var i, 98 | hintsList = [], 99 | results = []; 100 | if (prefix === ":") { 101 | prefix = ""; 102 | hintsList = parsedHints; 103 | } else { 104 | hintsList = parsedHints.concat(auxiliaryHints); 105 | } 106 | for (i = 0; i < hintsList.length; i++) { 107 | if (_.startsWith(hintsList[i].str, prefix)) { 108 | var displayName = hintsList[i].str.replace( 109 | new RegExp(StringUtils.regexEscape(prefix), "i"), 110 | "$&" 111 | ); 112 | results.push($("").addClass("RustIDE-hints") 113 | .addClass("RustIDE-hints-" + hintsList[i].type) 114 | .html(displayName)); 115 | } 116 | } 117 | return { 118 | hints: results, 119 | match: "", 120 | selectInitial: true, 121 | handleWideResults: false 122 | }; 123 | } 124 | 125 | function resolveHints(deferredData, token) { 126 | var deferred = new $.Deferred(); 127 | deferredData.done(function (data) { 128 | console.log("data:", data); 129 | var result, 130 | parsedHintsList = extractHints(data); 131 | cachedHints = parsedHintsList; 132 | needNewHints = false; 133 | result = buildHints(parsedHintsList, token); 134 | deferred.resolve(result); 135 | }).fail(function (e) { 136 | console.error("e:", e); 137 | }); 138 | return deferred; 139 | } 140 | 141 | this.hasHints = function (editor, implicitChar) { 142 | _cm = editor._codeMirror; 143 | if (_validToken(implicitChar)) { 144 | return true; 145 | } else { 146 | needNewHints = true; 147 | cachedHints = null; 148 | return false; 149 | } 150 | }; 151 | 152 | this.getHints = function (implicitChar) { 153 | 154 | var cursor = _cm.getCursor(), 155 | txt = _cm.getValue(); 156 | lastToken = _cm.getTokenAt(cursor); 157 | 158 | //implicitChar is null when press Backspace 159 | if (_validToken(implicitChar) || _validToken(lastToken.string)) { 160 | var tokenType = lastToken.type; 161 | if (["string", "comment", "meta", "def"].indexOf(tokenType) > -1) { 162 | return false; 163 | } 164 | if (needNewHints || (preTokenStr[0] !== lastToken.string[0]) || implicitChar === ":") { 165 | console.log("Asking Hints"); 166 | needNewHints = true; 167 | cachedHints = null; 168 | preTokenStr = lastToken.string; 169 | return resolveHints(RacerCli.getHintsD(txt, cursor, ++vpet), lastToken); 170 | } 171 | if (cachedHints) { 172 | return buildHints(cachedHints, lastToken); 173 | 174 | } 175 | return false; 176 | 177 | } else { 178 | return false; 179 | } 180 | }; 181 | 182 | this.insertHint = function ($hint) { 183 | if (!$hint) { 184 | throw new TypeError("Must provide valid hint and hints object as they are returned by calling getHints"); 185 | } else { 186 | console.log("$hint: " + $hint.text()); 187 | _cm.replaceSelection($hint.text().substring(prefix.length)); 188 | } 189 | }; 190 | } 191 | 192 | function RustDefinitionProvider() { 193 | 194 | // FIX-ME: cm's method might fail 195 | // CodeMirror' line and ch both start from 0, but brackets' start from 1, so is racer's 196 | function getDefinitionEndline(txt, startLine, startChar) { 197 | var result, 198 | tmpCm = CodeMirror($("")[0], { 199 | value: txt, 200 | mode: "rust" 201 | }); 202 | 203 | var pos = CodeMirror.Pos(startLine - 1, startChar - 1); 204 | 205 | result = tmpCm.findMatchingBracket(pos, false); 206 | if (result) { 207 | return result.to.line + 1; 208 | } else { 209 | console.log("fail to get endline using codemirror"); 210 | // try the old method 211 | var lines = txt.split("\n"), 212 | firstLine = lines[startLine - 1], 213 | i = 0, 214 | l = 0; 215 | for (i = 0; i < firstLine.length; i++) { 216 | if ([" ", "\t"].indexOf(firstLine[i]) < 0) { 217 | break; 218 | } 219 | } 220 | for (l = startLine; l < lines.length; l++) { 221 | if (lines[l][i] === "}") { 222 | break; 223 | } 224 | } 225 | return l; 226 | } 227 | } 228 | 229 | function resolveDefinition(deferredData, hostEditor) { 230 | var deferred = new $.Deferred(), 231 | defs, 232 | defItem, 233 | path; 234 | 235 | deferredData.done(function(data){ 236 | console.log("data:", data); 237 | defs = data.split("\n"); 238 | // Don't provide def when racer returns END 239 | if (defs[0] === "END") { 240 | deferred.reject(); 241 | return null; 242 | } 243 | 244 | // only use the first match for simplicity 245 | defItem = RacerCli.parse(defs[0]); 246 | path = defItem.path; 247 | 248 | // Don't provide def when its a module 249 | if (defItem.type === "Module") { 250 | deferred.reject(); 251 | } 252 | 253 | // TO-DO: consider use FileUtils.convertWindowsPathToUnixPath(); 254 | if (!FileSystem.isAbsolutePath(path)) { 255 | path = path.split("\\").join("/"); 256 | } 257 | 258 | DocumentManager.getDocumentForPath(path).done(function (doc) { 259 | var lineStart = Number(defItem.line), 260 | // doc._text might be null 261 | lineEnd = getDefinitionEndline(doc._text || doc.file._contents, lineStart, defItem.firstLine.length); 262 | 263 | var ranges = [ 264 | { 265 | document: doc, 266 | name: "", 267 | lineStart: lineStart - 1, 268 | lineEnd: lineEnd 269 | } 270 | ]; 271 | try { 272 | var rustInlineEditor = new MultiRangeInlineEditor(ranges); 273 | rustInlineEditor.load(hostEditor); 274 | deferred.resolve(rustInlineEditor); 275 | } catch (e) { 276 | console.error("[RustDefinitionProvidre] Error of get def", e); 277 | } 278 | }).fail(function (e) { 279 | console.error("[RustDefinitionProvidre] Error of get from path e:", e); 280 | }); 281 | }).fail(function (e) { 282 | console.error("e:", e); 283 | }); 284 | 285 | return deferred; 286 | } 287 | 288 | this.provider = function (hostEditor, pos) { 289 | var filePath = hostEditor.document.file.fullPath; 290 | if (["text/x-rustsrc", "rust"].indexOf(hostEditor.getModeForSelection()) < 0) { 291 | return null; 292 | } 293 | var sel = hostEditor.getSelection(); 294 | if (sel.start.line !== sel.end.line) { 295 | return null; 296 | } 297 | // not always work 298 | var newPos = { 299 | line: pos.line + 1, 300 | ch: pos.ch + 1 301 | }; 302 | console.log("Asking Definition"); 303 | return resolveDefinition(RacerCli.getDefD("", newPos, ++vpet, filePath), hostEditor); 304 | }; 305 | } 306 | 307 | exports.RustHintProvider = RustHintProvider; 308 | exports.RustDefinitionProvider = RustDefinitionProvider; 309 | 310 | }); 311 | -------------------------------------------------------------------------------- /src/RustUtils.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012 Adobe Systems Incorporated. All rights reserved. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | * 22 | */ 23 | 24 | /*jslint vars: true, plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */ 25 | /*global define, $, brackets */ 26 | 27 | 28 | // base on JSUtils.js in https://github.com/adobe/brackets/blob/5ef84133cb8c5acdcb7e80b85bbee86f65c2c9b1/src/language/JSUtils.js 29 | 30 | 31 | // TO-DO: write unit-test 32 | // see https://github.com/adobe/brackets/blob/5ef84133cb8c5acdcb7e80b85bbee86f65c2c9b1/test/spec/JSUtils-test.js 33 | // and https://github.com/adobe/brackets/wiki/Debugging-Test-Windows-in-Unit-Tests 34 | 35 | 36 | define(function (require, exports, module) { 37 | "use strict"; 38 | 39 | var _ = brackets.getModule("thirdparty/lodash"); 40 | 41 | // Load brackets modules 42 | var CodeMirror = brackets.getModule("thirdparty/CodeMirror2/lib/codemirror"), 43 | 44 | Async = brackets.getModule("utils/Async"), 45 | DocumentManager = brackets.getModule("document/DocumentManager"), 46 | ChangedDocumentTracker = brackets.getModule("document/ChangedDocumentTracker"), 47 | FileSystem = brackets.getModule("filesystem/FileSystem"), 48 | FileUtils = brackets.getModule("file/FileUtils"), 49 | PerfUtils = brackets.getModule("utils/PerfUtils"), 50 | StringUtils = brackets.getModule("utils/StringUtils"); 51 | 52 | 53 | /** 54 | * Tracks dirty documents between invocations of findMatchingFunctions. 55 | * @type {ChangedDocumentTracker} 56 | */ 57 | var _changedDocumentTracker = new ChangedDocumentTracker(); 58 | 59 | /** 60 | * Function matching regular expression. Recognizes the forms: 61 | * "function functionName()", "functionName = function()", and 62 | * "functionName: function()". 63 | * 64 | * Note: JavaScript identifier matching is not strictly to spec. This 65 | * RegExp matches any sequence of characters that is not whitespace. 66 | * @type {RegExp} 67 | */ 68 | var _functionRegExp = /(extern\s+)?("((?:\\.|[^"\\])*)"\s+)?(fn\s+([$_A-Za-z\u007F-\uFFFF][$_A-Za-z0-9\u007F-\uFFFF]*)(<[^>]*>)?\s*(\([^)]*\)))/g; 69 | 70 | /** 71 | * @private 72 | * Return an object mapping function name to offset info for all functions in the specified text. 73 | * Offset info is an array, since multiple functions of the same name can exist. 74 | * @param {!string} text Document text 75 | * @return {Object.} 76 | */ 77 | function _findAllFunctionsInText(text) { 78 | var results = {}, 79 | functionName, 80 | match; 81 | 82 | PerfUtils.markStart(PerfUtils.RustUTILS_REGEXP); 83 | 84 | while ((match = _functionRegExp.exec(text)) !== null) { 85 | functionName = (match[2] || match[5]).trim(); 86 | 87 | if (!Array.isArray(results[functionName])) { 88 | results[functionName] = []; 89 | } 90 | 91 | results[functionName].push({ 92 | offsetStart: match.index 93 | }); 94 | } 95 | 96 | PerfUtils.addMeasurement(PerfUtils.RustUTILS_REGEXP); 97 | 98 | return results; 99 | } 100 | 101 | // Given the start offset of a function definition (before the opening brace), find 102 | // the end offset for the function (the closing "}"). Returns the position one past the 103 | // close brace. Properly ignores braces inside comments, strings, and regexp literals. 104 | function _getFunctionEndOffset(text, offsetStart) { 105 | var mode = CodeMirror.getMode({}, "rust"); 106 | var state = CodeMirror.startState(mode), 107 | stream, style, token; 108 | var curOffset = offsetStart, 109 | length = text.length, 110 | blockCount = 0, 111 | lineStart; 112 | var foundStartBrace = false; 113 | 114 | // Get a stream for the next line, and update curOffset and lineStart to point to the 115 | // beginning of that next line. Returns false if we're at the end of the text. 116 | function nextLine() { 117 | if (stream) { 118 | curOffset++; // account for \n 119 | if (curOffset >= length) { 120 | return false; 121 | } 122 | } 123 | lineStart = curOffset; 124 | var lineEnd = text.indexOf("\n", lineStart); 125 | if (lineEnd === -1) { 126 | lineEnd = length; 127 | } 128 | stream = new CodeMirror.StringStream(text.slice(curOffset, lineEnd)); 129 | return true; 130 | } 131 | 132 | // Get the next token, updating the style and token to refer to the current 133 | // token, and updating the curOffset to point to the end of the token (relative 134 | // to the start of the original text). 135 | function nextToken() { 136 | if (curOffset >= length) { 137 | return false; 138 | } 139 | if (stream) { 140 | // Set the start of the next token to the current stream position. 141 | stream.start = stream.pos; 142 | } 143 | while (!stream || stream.eol()) { 144 | if (!nextLine()) { 145 | return false; 146 | } 147 | } 148 | style = mode.token(stream, state); 149 | token = stream.current(); 150 | curOffset = lineStart + stream.pos; 151 | return true; 152 | } 153 | 154 | while (nextToken()) { 155 | if (style !== "comment" && style !== "regexp" && style !== "string") { 156 | if (token === "{") { 157 | foundStartBrace = true; 158 | blockCount++; 159 | } else if (token === "}") { 160 | blockCount--; 161 | } 162 | } 163 | 164 | // blockCount starts at 0, so we don't want to check if it hits 0 165 | // again until we've actually gone past the start of the function body. 166 | if (foundStartBrace && blockCount <= 0) { 167 | return curOffset; 168 | } 169 | } 170 | 171 | // Shouldn't get here, but if we do, return the end of the text as the offset. 172 | return length; 173 | } 174 | 175 | /** 176 | * @private 177 | * Computes function offsetEnd, lineStart and lineEnd. Appends a result record to rangeResults. 178 | * @param {!Document} doc 179 | * @param {!string} functionName 180 | * @param {!Array.<{offsetStart: number, offsetEnd: number}>} functions 181 | * @param {!Array.<{document: Document, name: string, lineStart: number, lineEnd: number}>} rangeResults 182 | */ 183 | function _computeOffsets(doc, functionName, functions, rangeResults) { 184 | var text = doc.getText(), 185 | lines = StringUtils.getLines(text); 186 | 187 | functions.forEach(function (funcEntry) { 188 | if (!funcEntry.offsetEnd) { 189 | PerfUtils.markStart(PerfUtils.RustUTILS_END_OFFSET); 190 | 191 | funcEntry.offsetEnd = _getFunctionEndOffset(text, funcEntry.offsetStart); 192 | funcEntry.lineStart = StringUtils.offsetToLineNum(lines, funcEntry.offsetStart); 193 | funcEntry.lineEnd = StringUtils.offsetToLineNum(lines, funcEntry.offsetEnd); 194 | 195 | PerfUtils.addMeasurement(PerfUtils.RustUTILS_END_OFFSET); 196 | } 197 | 198 | rangeResults.push({ 199 | document: doc, 200 | name: functionName, 201 | lineStart: funcEntry.lineStart, 202 | lineEnd: funcEntry.lineEnd 203 | }); 204 | }); 205 | } 206 | 207 | /** 208 | * @private 209 | * Read a file and build a function list. Result is cached in fileInfo. 210 | * @param {!FileInfo} fileInfo File to parse 211 | * @param {!$.Deferred} result Deferred to resolve with all functions found and the document 212 | */ 213 | function _readFile(fileInfo, result) { 214 | DocumentManager.getDocumentForPath(fileInfo.fullPath) 215 | .done(function (doc) { 216 | var allFunctions = _findAllFunctionsInText(doc.getText()); 217 | 218 | // Cache the result in the fileInfo object 219 | fileInfo.RustUtils = {}; 220 | fileInfo.RustUtils.functions = allFunctions; 221 | fileInfo.RuUtils.timestamp = doc.diskTimestamp; 222 | 223 | result.resolve({ 224 | doc: doc, 225 | functions: allFunctions 226 | }); 227 | }) 228 | .fail(function (error) { 229 | result.reject(error); 230 | }); 231 | } 232 | 233 | 234 | /** 235 | * Determines if the document function cache is up to date. 236 | * @param {FileInfo} fileInfo 237 | * @return {$.Promise} A promise resolved with true with true when a function cache is available for the document. Resolves 238 | * with false when there is no cache or the cache is stale. 239 | */ 240 | function _shouldGetFromCache(fileInfo) { 241 | var result = new $.Deferred(), 242 | isChanged = _changedDocumentTracker.isPathChanged(fileInfo.fullPath); 243 | 244 | if (isChanged && fileInfo.RustUtils) { 245 | var doc = DocumentManager.getOpenDocumentForPath(fileInfo.fullPath); 246 | 247 | if (doc && doc.isDirty) { 248 | result.resolve(false); 249 | } else { 250 | var file = FileSystem.getFileForPath(fileInfo.fullPath); 251 | 252 | file.stat(function (err, stat) { 253 | if (!err) { 254 | result.resolve(fileInfo.RustUtils.timestamp.getTime() === stat.mtime.getTime()); 255 | } else { 256 | result.reject(err); 257 | } 258 | }); 259 | } 260 | 261 | } else { 262 | result.resolve(!isChanged && fileInfo.RustUtils); 263 | } 264 | 265 | return result.promise(); 266 | } 267 | 268 | /** 269 | * @private 270 | * Compute lineStart and lineEnd for each matched function 271 | * @param {!Array.<{doc: Document, fileInfo: FileInfo, functions: Array.}>} docEntries 272 | * @param {!string} functionName 273 | * @param {!Array.} rangeResults 274 | * @return {$.Promise} A promise resolved with an array of document ranges to populate a MultiRangeInlineEditor. 275 | */ 276 | function _getOffsetsForFunction(docEntries, functionName) { 277 | // Filter for documents that contain the named function 278 | var result = new $.Deferred(), 279 | matchedDocuments = [], 280 | rangeResults = []; 281 | 282 | docEntries.forEach(function (docEntry) { 283 | // Need to call _.has here since docEntry.functions could have an 284 | // entry for "hasOwnProperty", which results in an error if trying 285 | // to invoke docEntry.functions.hasOwnProperty(). 286 | if (_.has(docEntry.functions, functionName)) { 287 | var functionsInDocument = docEntry.functions[functionName]; 288 | matchedDocuments.push({ 289 | doc: docEntry.doc, 290 | fileInfo: docEntry.fileInfo, 291 | functions: functionsInDocument 292 | }); 293 | } 294 | }); 295 | 296 | Async.doInParallel(matchedDocuments, function (docEntry) { 297 | var doc = docEntry.doc, 298 | oneResult = new $.Deferred(); 299 | 300 | // doc will be undefined if we hit the cache 301 | if (!doc) { 302 | DocumentManager.getDocumentForPath(docEntry.fileInfo.fullPath) 303 | .done(function (fetchedDoc) { 304 | _computeOffsets(fetchedDoc, functionName, docEntry.functions, rangeResults); 305 | }) 306 | .always(function () { 307 | oneResult.resolve(); 308 | }); 309 | } else { 310 | _computeOffsets(doc, functionName, docEntry.functions, rangeResults); 311 | oneResult.resolve(); 312 | } 313 | 314 | return oneResult.promise(); 315 | }).done(function () { 316 | result.resolve(rangeResults); 317 | }); 318 | 319 | return result.promise(); 320 | } 321 | 322 | /** 323 | * Resolves with a record containing the Document or FileInfo and an Array of all 324 | * function names with offsets for the specified file. Results may be cached. 325 | * @param {FileInfo} fileInfo 326 | * @return {$.Promise} A promise resolved with a document info object that 327 | * contains a map of all function names from the document and each function's start offset. 328 | */ 329 | function _getFunctionsForFile(fileInfo) { 330 | var result = $.Deferred(); 331 | 332 | _shouldGetFromCache(fileInfo).done(function (useCache) { 333 | if (useCache) { 334 | result.resolve({ 335 | fileInfo: fileInfo, 336 | functions: fileInfo.RustUtils.functions 337 | }); 338 | } else { 339 | _readFile(fileInfo, result); 340 | } 341 | }).fail(function (err) { 342 | result.reject(err); 343 | }); 344 | 345 | return result.promise(); 346 | } 347 | 348 | /** 349 | * @private 350 | * Get all functions for each FileInfo. 351 | * @param {Array.} fileInfos 352 | * @return {$.Promise} A promise resolved with an array of document info objects that each 353 | * contain a map of all function names from the document and each function's start offset. 354 | */ 355 | function _getFunctionsInFiles(fileInfos) { 356 | var result = new $.Deferred(), 357 | docEntries = []; 358 | Async.doInParallel(fileInfos, function (fileInfo) { 359 | var oneResult = new $.Deferred(); 360 | 361 | _getFunctionsForFile(fileInfo) 362 | .done(function (docInfo) { 363 | docEntries.push(docInfo); 364 | }).always(function (error) { 365 | oneResult.resolve(); 366 | }); 367 | 368 | return oneResult.promise(); 369 | }).always(function () { 370 | _changedDocumentTracker.reset(); 371 | result.resolve(docEntries); 372 | }); 373 | 374 | return result.promise(); 375 | } 376 | 377 | /** 378 | * Return all functions that have the specified name, searching across all the given files. 379 | * 380 | * @param {!String} functionName The name to match. 381 | * @param {!Array.} fileInfos The array of files to search. 382 | * @param {boolean=} keepAllFiles If true, don't ignore non-javascript files. 383 | * @return {$.Promise} that will be resolved with an Array of objects containing the 384 | * source document, start line, and end line (0-based, inclusive range) for each matching function list. 385 | * Does not addRef() the documents returned in the array. 386 | */ 387 | function findMatchingFunctions(functionName, fileInfos, keepAllFiles) { 388 | var result = new $.Deferred(), 389 | rustFiles = []; 390 | 391 | if (!keepAllFiles) { 392 | rustFiles = fileInfos.filter(function (fileInfo) { 393 | return FileUtils.getFileExtension(fileInfo.fullPath).toLowerCase() === "rs"; 394 | }); 395 | } else { 396 | rustFiles = fileInfos; 397 | } 398 | 399 | _getFunctionsInFiles(rustFiles).done(function (docEntries) { 400 | _getOffsetsForFunction(docEntries, functionName).done(function (rangeResults) { 401 | result.resolve(rangeResults); 402 | }); 403 | }); 404 | return result.promise(); 405 | } 406 | 407 | /** 408 | * Finds all instances of the specified searchName in "text". 409 | * Returns an Array of Objects with start and end properties. 410 | * 411 | * @param text {!String} Rust text to search 412 | * @param searchName {!String} function name to search for 413 | * @return {Array.<{offset:number, functionName:string}>} 414 | * Array of objects containing the start offset for each matched function name. 415 | */ 416 | function findAllMatchingFunctionsInText(text, searchName) { 417 | var allFunctions = _findAllFunctionsInText(text); 418 | var result = []; 419 | var lines = text.split("\n"); 420 | 421 | _.forEach(allFunctions, function (functions, functionName) { 422 | if (functionName === searchName || searchName === "*") { 423 | functions.forEach(function (funcEntry) { 424 | var endOffset = _getFunctionEndOffset(text, funcEntry.offsetStart); 425 | result.push({ 426 | name: functionName, 427 | lineStart: StringUtils.offsetToLineNum(lines, funcEntry.offsetStart), 428 | lineEnd: StringUtils.offsetToLineNum(lines, endOffset) 429 | }); 430 | }); 431 | } 432 | }); 433 | 434 | return result; 435 | } 436 | 437 | 438 | PerfUtils.createPerfMeasurement("RustUTILS_GET_ALL_FUNCTIONS", "Parallel file search across project"); 439 | PerfUtils.createPerfMeasurement("RustUTILS_REGEXP", "RegExp search for all functions"); 440 | PerfUtils.createPerfMeasurement("RustUILS_END_OFFSET", "Find end offset for a single matched function"); 441 | 442 | exports.findAllMatchingFunctionsInText = findAllMatchingFunctionsInText; 443 | exports.getFunctionEndOffset = _getFunctionEndOffset; 444 | }); 445 | -------------------------------------------------------------------------------- /src/SyntaxColoring.js: -------------------------------------------------------------------------------- 1 | /*jslint vars: true, plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50, browser: true */ 2 | /*global define */ 3 | 4 | define(function (require, exports, module) { 5 | "use strict"; 6 | 7 | // Since brackets 1.4 will enable defineSimpleMode of CodeMirror, and the CodeMirror 8 | // rust.js is old, I write a new rust.js with defineSimpleMode, this line will be deleted 9 | // when the new version of brackets or the new version of codemirror release. 10 | require("src/CodeMirror/rust"); 11 | 12 | }); 13 | -------------------------------------------------------------------------------- /src/dialogs/RacerSettings.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Licensed under MIT 4 | * 5 | */ 6 | 7 | /*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */ 8 | /*global define, $, brackets, Mustache */ 9 | 10 | define(function (require, exports, module) { 11 | "use strict"; 12 | var PreferencesManager = brackets.getModule("preferences/PreferencesManager"), 13 | Dialogs = brackets.getModule("widgets/Dialogs"), 14 | CommandManager = brackets.getModule("command/CommandManager"), 15 | Menus = brackets.getModule("command/Menus"), 16 | Commands = brackets.getModule("command/Commands"); 17 | 18 | var prefs = PreferencesManager.getExtensionPrefs("Rust-IDE"); 19 | 20 | // this function copied and modifitied from zaggino.brackets-git 21 | function showRustIDEDialog() { 22 | var questionDialogTemplate = require("text!src/dialogs/templates/rust-ide-settings.html"); 23 | var compiledTemplate = Mustache.render(questionDialogTemplate, { 24 | title: "Rust-IDE Settings", 25 | question: "Set the absolute path of your racer bin file, e.g. 'D:\\racer\\target\\release\\racer.exe'", 26 | defaultValue: prefs.get("racerPath"), 27 | BUTTON_CANCEL: "Cancel", 28 | BUTTON_OK: "OK" 29 | }); 30 | var dialog = Dialogs.showModalDialogUsingTemplate(compiledTemplate); 31 | dialog.done(function (buttonId) { 32 | if (buttonId === "ok") { 33 | console.info("you click ok"); 34 | var $dialog = dialog.getElement(); 35 | $("*[settingsProperty]", $dialog).each(function () { 36 | var $this = $(this); 37 | prefs.set("racerPath", $this.val().trim()); 38 | }); 39 | } 40 | }); 41 | prefs.save(); 42 | } 43 | 44 | 45 | // First, register a command - a UI-less object associating an id to a handler 46 | var RUST_IDE_SETTINGS = "rustide.settings"; // package-style naming to avoid collisions 47 | CommandManager.register("Rust-IDE Settings", RUST_IDE_SETTINGS, showRustIDEDialog); 48 | 49 | // Then create a menu item bound to the command 50 | // The label of the menu item is the name we gave the command (see above) 51 | var menu = Menus.getMenu(Menus.AppMenuBar.FILE_MENU); 52 | menu.addMenuItem(RUST_IDE_SETTINGS, "", Menus.AFTER, Commands.FILE_PROJECT_SETTINGS); 53 | 54 | exports.show = showRustIDEDialog; 55 | 56 | }); 57 | -------------------------------------------------------------------------------- /src/dialogs/templates/rust-ide-settings.html: -------------------------------------------------------------------------------- 1 | 14 | -------------------------------------------------------------------------------- /src/node/LintDomain.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Licensed under MIT 4 | * 5 | */ 6 | 7 | /*jslint vars: true, plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50,node: true */ 8 | 9 | (function () { 10 | "use strict"; 11 | 12 | var child_process = require("child_process"); 13 | 14 | function _cmdGetLint(exec, cb) { 15 | child_process.exec(exec, function (err, stdout, stderr) { 16 | cb(null, stderr + stdout); 17 | }); 18 | } 19 | 20 | function init(manager) { 21 | if (!manager.hasDomain("rustLint")) { 22 | manager.registerDomain("rustLint", { 23 | major: 1, 24 | minor: 0 25 | }); 26 | } 27 | 28 | manager.registerCommand( 29 | "rustLint", 30 | "getLint", 31 | _cmdGetLint, 32 | true // true then `cb()`, false then `return` 33 | ); 34 | } 35 | 36 | exports.init = init; 37 | 38 | }()); 39 | -------------------------------------------------------------------------------- /src/node/RacerDomain.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012 Adobe Systems Incorporated. All rights reserved. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | * 22 | */ 23 | 24 | 25 | /*jslint vars: true, plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50,node: true */ 26 | 27 | 28 | (function () { 29 | "use strict"; 30 | 31 | var _domainManager, 32 | child_process = require("child_process"), 33 | fs = require("fs"), 34 | domainName = "RacerDomain", 35 | extName = "[rust-ide]"; 36 | 37 | 38 | // args: {txt, line, char, path, isPathTmp, command, event} 39 | function racerCli(racerPath, args, petition, cb) { 40 | try { 41 | var fname = args.path, 42 | output = "", 43 | err = ""; 44 | 45 | // use tmp file or not 46 | if (args.isPathTmp) { 47 | fname = fname + "tmp.racertmp"; 48 | fs.writeFileSync(fname, args.txt); 49 | } 50 | 51 | var racer = child_process.spawn( 52 | racerPath, [args.command, args.line, args.char, fname] 53 | ); 54 | 55 | racer.stdout.on("data", function (data) { 56 | output += data.toString(); 57 | }); 58 | 59 | racer.stderr.on("data", function (data) { 60 | err += data.toString(); 61 | }); 62 | 63 | racer.on("close", function (code) { 64 | cb(err, output); 65 | }); 66 | 67 | racer.unref(); 68 | 69 | } catch (e) { 70 | console.error(extName + e); 71 | } 72 | } 73 | 74 | // args: {txt, line, char, path} 75 | function cmdGetHint(racerPath, args, petition, cb) { 76 | args.command = "complete"; 77 | racerCli(racerPath, args, petition, cb); 78 | } 79 | 80 | function cmdFindDefinition(racerPath, args, petition, cb) { 81 | args.command = "find-definition"; 82 | racerCli(racerPath, args, petition, cb); 83 | } 84 | 85 | function init(domainManager) { 86 | console.info("Rust NodeDomain init"); 87 | if (!domainManager.hasDomain(domainName)) { 88 | domainManager.registerDomain(domainName, { 89 | major: 0, 90 | minor: 1 91 | }); 92 | } 93 | 94 | domainManager.registerCommand( 95 | domainName, // domain name 96 | "getHint", // command name 97 | cmdGetHint, // command handler function 98 | true, // asynchronous 99 | "Return Rust Hints", [ 100 | { 101 | name: "racerPath", 102 | type: "string", 103 | description: "absolute path to racer" 104 | }, 105 | { 106 | name: "args", 107 | type: "object", 108 | description: "{txt, line, char, path, isPathTmp}" 109 | }, 110 | { 111 | name: "petition", 112 | type: "number", 113 | description: "petition number" 114 | } 115 | ], [] 116 | ); 117 | 118 | domainManager.registerCommand( 119 | domainName, 120 | "findDef", 121 | cmdFindDefinition, 122 | true, 123 | "Return found definitions", [ 124 | { 125 | name: "racerPath", 126 | type: "string", 127 | description: "absolute path to racer" 128 | }, 129 | { 130 | name: "args", 131 | type: "object", 132 | description: "{txt, line, char, path, isPathTmp}" 133 | }, 134 | { 135 | name: "petition", 136 | type: "number", 137 | description: "petition number" 138 | } 139 | ], [] 140 | ); 141 | 142 | _domainManager = domainManager; 143 | 144 | } 145 | 146 | exports.init = init; 147 | 148 | }()); 149 | -------------------------------------------------------------------------------- /styles/main.css: -------------------------------------------------------------------------------- 1 | /* base on https://github.com/mackenza/Brackets-PHP-SmartHints */ 2 | 3 | .RustIDE-hints { 4 | padding-left: 22px; 5 | position: relative; 6 | } 7 | 8 | .RustIDE-hints:before { 9 | position: absolute; 10 | left: 2px; 11 | bottom: 2px; 12 | border-radius: 50%; 13 | font-size: 12px; 14 | font-weight: bold; 15 | height: 15px; 16 | width: 15px; 17 | line-height: 16px; 18 | text-align: center; 19 | color: white; 20 | box-sizing: border-box; 21 | content: "R"; 22 | background-color: #000 23 | } 24 | 25 | .RustIDE-hints-Keyword:before { 26 | content: "K"; 27 | background-color: #c800a4; 28 | } 29 | 30 | .RustIDE-hints-Macro:before { 31 | content: "m"; 32 | background-color: #3E999F; 33 | } 34 | 35 | .RustIDE-hints-Module:before { 36 | content: "M"; 37 | background-color: burlywood; 38 | } 39 | 40 | .RustIDE-hints-Trait:before { 41 | content: "T"; 42 | background-color: dodgerblue; 43 | } 44 | 45 | .RustIDE-hints-Struct:before { 46 | content: "S"; 47 | background-color: darkslateblue; 48 | } 49 | 50 | .RustIDE-hints-Function:before { 51 | content: "F"; 52 | background-color: darkgreen; 53 | } 54 | 55 | .RustIDE-hints-Enum:before { 56 | content: "E"; 57 | background-color: darkkhaki; 58 | } 59 | 60 | .RustIDE-hints-Let:before { 61 | content: "L"; 62 | background-color: darkorange; 63 | } 64 | 65 | .RustIDE-hints-EnumVariant:before { 66 | content: "EV"; 67 | background-color: darkslateblue; 68 | } 69 | 70 | .CodeMirror .rust-linter-gutter { 71 | width: 20px; 72 | } 73 | 74 | .CodeMirror .rust-linter-gutter-icon { 75 | margin-left: 7px; 76 | cursor: pointer; 77 | } 78 | 79 | .rust-linter-gutter-error { 80 | color: #822; 81 | } 82 | 83 | .rust-linter-gutter-warning { 84 | color: #f89406; 85 | } 86 | 87 | .CodeMirror *[class*="rust-linter-line-warning"] { 88 | border-bottom: 2px dotted #f89406; 89 | z-index: 6; 90 | } 91 | 92 | .CodeMirror *[class*="rust-linter-line-error"] { 93 | border-bottom: 2px dotted #bd362f; 94 | z-index: 7; 95 | } 96 | --------------------------------------------------------------------------------