From 653ea58edc33d0a4226551a283f76955f2c91279 Mon Sep 17 00:00:00 2001 From: Alex Butler Date: Fri, 21 Dec 2018 13:06:48 +0000 Subject: [PATCH 1/2] Format code --- lib/index.js | 58 ++++++++++++++++++++++------------------------------ 1 file changed, 24 insertions(+), 34 deletions(-) diff --git a/lib/index.js b/lib/index.js index c67cacc..8462d08 100644 --- a/lib/index.js +++ b/lib/index.js @@ -107,14 +107,12 @@ async function rustcSysroot(toolchain) { try { let { stdout } = await exec(`rustup run ${toolchain} rustc --print sysroot`) return stdout.trim() - } - catch (e) { + } catch (e) { // make an attempt to use system rustc try { let { stdout } = await exec(`rustc --print sysroot`) return stdout.trim() - } - catch (sys_e) { + } catch (sys_e) { throw e } } @@ -146,8 +144,7 @@ async function serverEnv(toolchain) { try { let sysroot = await rustcSysroot(toolchain) env.RUST_SRC_PATH = path.join(sysroot, "/lib/rustlib/src/rust/src/") - } - catch (e) { + } catch (e) { console.warn("Failed to find sysroot: " + e) } } @@ -161,8 +158,7 @@ async function serverEnv(toolchain) { async function installRlsComponents(toolchain) { try { await exec(`rustup component add rls-preview --toolchain ${toolchain}`) - } - catch (e) { + } catch (e) { let suggestedVersion if (toolchain.startsWith('nightly')) { // 'rls-preview' not available search for a decent suggestion @@ -190,8 +186,7 @@ async function installRlsComponents(toolchain) { try { await exec(`rustup component add rust-src --toolchain ${toolchain}`) await exec(`rustup component add rust-analysis --toolchain ${toolchain}`) - } - catch (e) { + } catch (e) { atom.notifications.addError(`\`rust-src\`/\`rust-analysis\` not found on \`${toolchain}\``, { dismissable: true }) @@ -210,7 +205,7 @@ function logSuspiciousStdout(process) { chunk.toString('utf8') .split('\n') .filter(l => l.trim() && - l.length < 10000 && // ignore long chunks, these are much more likely to be false positives + l.length < 10000 && // ignore long chunks, these are much more likely to be false positives !l.startsWith("Content-Length:") && !l.includes('"jsonrpc":"2.0"')) .forEach(line => console.error("Rust (RLS) suspicious stdout:", line)) @@ -253,8 +248,7 @@ async function checkRls(busySignalService) { try { return await _checkingRls - } - finally { + } finally { _checkingRls = null } } @@ -371,8 +365,7 @@ class RustLanguageClient extends AutoLanguageClient { try { await exec(`rustup run ${toolchain} rustc --version`) clearIdeRustInfos() - } - catch (e) { + } catch (e) { this._handleMissingToolchain(toolchain) throw e } @@ -385,8 +378,7 @@ class RustLanguageClient extends AutoLanguageClient { async _handleMissingToolchain(toolchain) { if (!await exec('rustup --version').catch(() => false)) { this._handleMissingRustup() - } - else if (await checkHasRls(toolchain)) { + } else if (await checkHasRls(toolchain)) { let clicked = await atomPrompt(`\`rustup\` missing ${toolchain} toolchain`, { detail: `rustup toolchain install ${toolchain}`, }, ['Install']) @@ -414,8 +406,7 @@ class RustLanguageClient extends AutoLanguageClient { ) } } - } - else { + } else { this._handleMissingToolchainMissingRls(toolchain) } } @@ -436,8 +427,7 @@ class RustLanguageClient extends AutoLanguageClient { if (toolchain === 'nightly') { note.description += ' Try using a previous _dated_ nightly.' - } - else if (toolchain.startsWith('nightly')) { + } else if (toolchain.startsWith('nightly')) { note.description += ' Try using another nightly version.' } @@ -457,8 +447,7 @@ class RustLanguageClient extends AutoLanguageClient { } }) } - } - catch (e) { + } catch (e) { console.warn(e) } @@ -491,8 +480,7 @@ class RustLanguageClient extends AutoLanguageClient { .then(() => this._restartLanguageServers()) .catch(logErr) } - } - catch (e) { + } catch (e) { e && console.warn(e) } } @@ -518,17 +506,20 @@ class RustLanguageClient extends AutoLanguageClient { // Watch config toolchain changes -> switch, install & update toolchains, restart servers this.disposables.add(atom.config.onDidChange('ide-rust.rlsToolchain', - _.debounce(({ newValue }) => { + _.debounce(async ({ newValue }) => { if (rlsCommandOverride()) { // don't bother checking toolchain if an override is being used return } - return this._checkToolchain() - .then(() => checkRls(this.busySignalService)) - .then(() => this._restartLanguageServers(`Switched Rls toolchain to \`${newValue}\``)) - .then(() => this._promptToUpdateToolchain()) - .catch(e => logErr(e, console.info)) + try { + await this._checkToolchain() + await checkRls(this.busySignalService) + await this._restartLanguageServers(`Switched Rls toolchain to \`${newValue}\``) + return this._promptToUpdateToolchain() + } catch (e) { + return logErr(e, console.info) + } }, 1000) )) @@ -649,8 +640,7 @@ class RustLanguageClient extends AutoLanguageClient { env: await serverEnv(toolchain), cwd: projectPath })) - } - catch (e) { + } catch (e) { throw new Error("failed to start server: " + e) } } @@ -699,7 +689,7 @@ if (process.platform === "win32") { RustLanguageClient.prototype._handleMissingRustup = () => { atomPrompt("`rustup` is not available", { description: "`rustup` is required for ide-rust functionality. " + - "**Install from https://www.rustup.rs and restart atom**." + "**Install from https://www.rustup.rs and restart atom**." }) } } From 76b8901e4dd465fbd23cca2edd88ccc99a8f4faa Mon Sep 17 00:00:00 2001 From: Alex Butler Date: Fri, 21 Dec 2018 16:43:21 +0000 Subject: [PATCH 2/2] Add multi-crate project detection with workarounds --- lib/rls-project.js | 132 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 121 insertions(+), 11 deletions(-) diff --git a/lib/rls-project.js b/lib/rls-project.js index 4e80993..606c445 100644 --- a/lib/rls-project.js +++ b/lib/rls-project.js @@ -20,6 +20,17 @@ class RlsProject { /** @type {?BusyMessage} */ this._rustDocBusyMessage = null + this._disposable = atom.notifications.onDidAddNotification(async note => { + if (this._disposable && + (!this.server || + !this.server.connection || + !this.server.connection.isConnected)) { + this._disposable.dispose() + return + } + + await handleMultiCrateProjectErrors(this.server.projectPath, note) + }) // Rls (>= 2018-02-24) sends `window/progress` notifications // see https://github.com/Microsoft/language-server-protocol/pull/245/files @@ -27,14 +38,19 @@ class RlsProject { const busySignal = this.getBusySignalService() if (!busySignal) return - let { id, title, message, percentage, done } = params + let { + id, + title, + message, + percentage, + done + } = params let busyMessage = this._progress.get(id) if (done) { if (busyMessage) busyMessage.dispose() this._progress.delete(id) - } - else { + } else { let busyText = `${path.basename(this.server.projectPath)} RLS ${title.toLowerCase()}` if (busyMessage) { // use previous percentages/messages according to the spec @@ -46,8 +62,7 @@ class RlsProject { if (busyMessage) { busyMessage.setTitle(busyText) - } - else { + } else { busyMessage = busySignal.reportBusy(busyText) this._progress.set(id, busyMessage) } @@ -68,8 +83,7 @@ class RlsProject { server.connection.onCustom('rustDocument/beginBuild', () => { if (this._rustDocBusyMessage) { this._rustDocBusyMessage.count += 1 - } - else { + } else { let busySignal = this.getBusySignalService() if (busySignal) { this._rustDocBusyMessage = busySignal @@ -83,8 +97,8 @@ class RlsProject { this._rustDocBusyMessage.count -= 1 if (this._rustDocBusyMessage.count === 0) { - this._rustDocBusyMessage.dispose() - this._rustDocBusyMessage = null + this._rustDocBusyMessage.dispose() + this._rustDocBusyMessage = null } } }) @@ -114,7 +128,9 @@ class RlsProject { if (_.isEqual(config, this._lastSentConfig)) return this.server.connection.didChangeConfiguration({ - settings: { rust: config } + settings: { + rust: config + } }) this._lastSentConfig = config }) @@ -122,7 +138,11 @@ class RlsProject { // Default Rls config according to package settings & Rls defaults defaultConfig() { - const { allTargets, clippyPreference } = atom.config.get("ide-rust.rlsDefaultConfig") + const { + allTargets, + clippyPreference + } = atom.config.get("ide-rust.rlsDefaultConfig") + const rlsConfig = {} if (allTargets === "On" || allTargets === "Off") { rlsConfig.all_targets = allTargets === "On" @@ -134,4 +154,94 @@ class RlsProject { } } +/** + * Converts fs async callback functions to use promises + * @param {function} functionWithCallback + * @return {function} async function + */ +function callbackAsync(functionWithCallback) { + return async (...args) => { + return new Promise((resolve, reject) => { + functionWithCallback(...args, (err, ...out) => { + if (err) { + reject(err) + } else { + resolve(...out) + } + }) + }) + } +} + +const asyncLstat = callbackAsync(fs.lstat) + +/** + * Check error notifications to see if the cause is a multi-crate project & offer help. + * + * See https://github.com/rust-lang/atom-ide-rust#multi-crate-projects + * + * @param {string} projectPath + * @param {Notification} errorNote + */ +async function handleMultiCrateProjectErrors(projectPath, errorNote) { + const options = errorNote.options || {} + const detail = options.detail || '' + + if (options._src !== 'ide-rust' && + errorNote.getType() === 'error' && + (errorNote.getMessage() || '').startsWith('could not find `Cargo.toml`') && + detail.endsWith(projectPath)) { + + let root_manifest = await (asyncLstat(path.join(projectPath, 'Cargo.toml')).catch(() => false)) + if (root_manifest) { + return + } + + try { + const ls = await callbackAsync(fs.readdir)(projectPath) + const childProjects = [] + for (const f of ls) { + let file = path.join(projectPath, f) + let stat = await asyncLstat(file) + if (stat.isDirectory()) { + let has_manifest = await (asyncLstat(path.join(file, 'Cargo.toml')).catch(() => false)) + if (has_manifest) { + childProjects.push(f) + } + } + } + + if (childProjects.length) { + let newNote + const projects = childProjects.map(p => `"${p}"`).join(', ') + const workspaceManifest = `[workspace]\nmembers = [${projects}]` + let options = { + _src: 'ide-rust', + dismissable: true, + description: `Child projects without a root (or higher) workspace are not supported. A root manifest at _${path.join(projectPath, 'Cargo.toml')}_ could allow RLS to build the projects as a workspace.\n\nSee [atom-ide-rust#multi-crate-projects](https://github.com/rust-lang/atom-ide-rust#multi-crate-projects)`, + buttons: [{ + text: 'Add workspace Cargo.toml', + onDidClick: async () => { + await callbackAsync(fs.writeFile)(path.join(projectPath, 'Cargo.toml'), workspaceManifest) + newNote.dismiss() + errorNote.dismiss() + } + }, { + text: 'Ignore project', + onDidClick: () => { + const ignoredPaths = atom.config.get('ide-rust.ignoredProjectPaths') + atom.config.set('ide-rust.ignoredProjectPaths', [ignoredPaths, projectPath].join(', ')) + newNote.dismiss() + errorNote.dismiss() + } + }] + } + newNote = atom.notifications.addInfo('Multi-crate project detected', options) + } + } catch (e) { + console.warn(e) + } + } +} + module.exports = RlsProject