diff --git a/resources/unpacked/devtools/front_end/console/DiracPrompt.js b/resources/unpacked/devtools/front_end/console/DiracPrompt.js index 247b334c9b..cd89da4f97 100644 --- a/resources/unpacked/devtools/front_end/console/DiracPrompt.js +++ b/resources/unpacked/devtools/front_end/console/DiracPrompt.js @@ -400,6 +400,19 @@ WebInspector.DiracPromptWithHistory.prototype = { return; } + const suggestStyle = (style = "") => `suggest-cljs ${style}`; + + const namespaceSelector = name => { + return function(namespaceDescriptors) { + return namespaceDescriptors[name]; + } + }; + const selectCurrentNamespace = namespaceSelector(this._currentClojureScriptNamespace); + + const concatAnnotatedResults = results => { + return [].concat.apply([], results); + }; + const lastSlashIndex = input.lastIndexOf("/"); if (lastSlashIndex >= 0) { // completion of fully qualified name => split at last slash @@ -409,21 +422,41 @@ WebInspector.DiracPromptWithHistory.prototype = { // namespace <= "some.namespace" // // present only symbols from given namespace, matching given prefix + // note that some.namespace may be also alias to a namespace or a macro namespace, we will resolve it const prefix = input.substring(lastSlashIndex + 1); const expression = input.substring(0, lastSlashIndex + 1); const namespace = input.substring(0, lastSlashIndex); - const annotateQualifiedSymbols = symbols => { + const annotateQualifiedSymbols = (style, symbols) => { return symbols.filter(symbol => symbol.startsWith(prefix)).map(symbol => ({ title: symbol || "?", - className: "suggest-cljs-qualified" + className: suggestStyle(style) })); }; - return dirac.extractNamespaceSymbolsAsync(namespace) - .then(annotateQualifiedSymbols) - .then(completionsReadyCallback.bind(this, expression)); + const currentNamespaceDescriptorPromise = dirac.extractNamespacesAsync().then(selectCurrentNamespace); + + const resolvedNamespacePromise = currentNamespaceDescriptorPromise.then(currentNamespaceDescriptor => { + if (!currentNamespaceDescriptor) { + return namespace; + } + const namespaceAliases = currentNamespaceDescriptor.namespaceAliases || {}; + const macroNamespaceAliases = currentNamespaceDescriptor.macroNamespaceAliases || {}; + const allAliases = Object.assign({}, namespaceAliases, macroNamespaceAliases); + return allAliases[namespace] || namespace; // resolve alias or assume namespace name is a full namespace name + }); + + const namespaceSymbolsPromise = resolvedNamespacePromise.then(dirac.extractNamespaceSymbolsAsync).then(annotateQualifiedSymbols.bind(this, "suggest-cljs-qualified")); + const macroNamespaceSymbolsPromise = resolvedNamespacePromise.then(dirac.extractMacroNamespaceSymbolsAsync).then(annotateQualifiedSymbols.bind(this, "suggest-cljs-qualified suggest-cljs-macro")); + + // order matters here, see _markAliasedCompletions below + const jobs = [ + namespaceSymbolsPromise, + macroNamespaceSymbolsPromise + ]; + + Promise.all(jobs).then(concatAnnotatedResults).then(completionsReadyCallback.bind(this, expression)); } else { // general completion (without slashes) // combine: locals (if paused in debugger), current ns symbols, namespace names and cljs.core symbols @@ -432,7 +465,7 @@ WebInspector.DiracPromptWithHistory.prototype = { const annotateSymbols = (style, symbols) => { return symbols.filter(symbol => symbol.startsWith(input)).map(symbol => ({ title: symbol || "?", - className: style + className: suggestStyle(style) })); }; @@ -472,34 +505,74 @@ WebInspector.DiracPromptWithHistory.prototype = { const filteredLocals = locals.filter(item => item.name.startsWith(input)); const annotatedCompletions = filteredLocals.map(item => ({ title: item.name || "?", - info: item.identifier ? "js/" + item.identifier : undefined, - className: "suggest-cljs-scope" + epilogue: item.identifier ? "js/" + item.identifier : undefined, + className: suggestStyle("suggest-cljs-scope") })); annotatedCompletions.reverse(); // we want to display inner scopes first return annotatedCompletions; }; - const annotateNamespaces = namespaces => { + const annotateNamespaceNames = (style, namespaces) => { return namespaces.filter(name => name.startsWith(input)).map(name => ({ title: name || "?", - className: "suggest-cljs-ns" + className: suggestStyle(style) })); }; - const concatAnnotatedResults = results => { - return [].concat.apply([], results); + const extractNamespaceNames = namespaceDescriptors => { + return Object.keys(namespaceDescriptors); }; - const extractNamespaceNames = namespaceDescriptors => { - return namespaceDescriptors.map(descriptor => descriptor.name); + const extractMacroNamespaceNames = namespaceDescriptors => { + let names = []; + for (let descriptor of Object.values(namespaceDescriptors)) { + if (!descriptor.detectedMacroNamespaces) { + continue; + } + names = names.concat(descriptor.detectedMacroNamespaces); + } + return dirac.deduplicate(names); + }; + + const annotateAliasesOrRefers = (kind, prefix, style, namespaceDescriptor) => { + if (!namespaceDescriptor) { + return []; + } + const mapping = namespaceDescriptor[kind]; + return Object.keys(mapping).filter(name => name.startsWith(input)).map(name => { + const targetName = mapping[name]; + return { + title: name, + epilogue: targetName ? prefix + targetName : null, // full target name + className: suggestStyle(style) + } + }); }; const localsPromise = dirac.extractScopeInfoFromScopeChainAsync(debuggerModel.selectedCallFrame()).then(extractAndAnnotateLocals); - const currentNSSymbolsPromise = dirac.extractNamespaceSymbolsAsync(this._currentClojureScriptNamespace).then(annotateSymbols.bind(this, "suggest-cljs-in-ns")); - const namespacesPromise = dirac.extractNamespacesAsync().then(extractNamespaceNames).then(annotateNamespaces); - const cljsCoreNSSymbolsPromise = dirac.extractNamespaceSymbolsAsync("cljs.core").then(annotateSymbols.bind(this, "suggest-cljs-core")); + const currentNamespaceSymbolsPromise = dirac.extractNamespaceSymbolsAsync(this._currentClojureScriptNamespace).then(annotateSymbols.bind(this, "suggest-cljs-in-ns")); + const namespaceNamesPromise = dirac.extractNamespacesAsync().then(extractNamespaceNames).then(annotateNamespaceNames.bind(this, "suggest-cljs-ns")); + const macroNamespaceNamesPromise = dirac.extractNamespacesAsync().then(extractMacroNamespaceNames).then(annotateNamespaceNames.bind(this, "suggest-cljs-ns suggest-cljs-macro")); + const coreNamespaceSymbolsPromise = dirac.extractNamespaceSymbolsAsync("cljs.core").then(annotateSymbols.bind(this, "suggest-cljs-core")); + const currentNamespaceDescriptor = dirac.extractNamespacesAsync().then(selectCurrentNamespace); + const namespaceAliasesPromise = currentNamespaceDescriptor.then(annotateAliasesOrRefers.bind(this, "namespaceAliases", "as ", "suggest-ns-alias")); + const macroNamespaceAliasesPromise = currentNamespaceDescriptor.then(annotateAliasesOrRefers.bind(this, "macroNamespaceAliases", "as ", "suggest-ns-alias suggest-cljs-macro")); + const namespaceRefersPromise = currentNamespaceDescriptor.then(annotateAliasesOrRefers.bind(this, "namespaceRefers", "in ", "suggest-refer")); + const macroRefersPromise = currentNamespaceDescriptor.then(annotateAliasesOrRefers.bind(this, "macroRefers", "in ", "suggest-refer suggest-cljs-macro")); + + // order matters here, see _markAliasedCompletions below + const jobs = [ + localsPromise, + currentNamespaceSymbolsPromise, + namespaceRefersPromise, + macroRefersPromise, + namespaceAliasesPromise, + macroNamespaceAliasesPromise, + namespaceNamesPromise, + macroNamespaceNamesPromise, + coreNamespaceSymbolsPromise + ]; - const jobs = [localsPromise, currentNSSymbolsPromise, namespacesPromise, cljsCoreNSSymbolsPromise]; Promise.all(jobs).then(concatAnnotatedResults).then(completionsReadyCallback.bind(this, "")); } }, diff --git a/resources/unpacked/devtools/front_end/dirac/dirac.js b/resources/unpacked/devtools/front_end/dirac/dirac.js index bcdcab9ae3..d7bf5af6b4 100644 --- a/resources/unpacked/devtools/front_end/dirac/dirac.js +++ b/resources/unpacked/devtools/front_end/dirac/dirac.js @@ -197,6 +197,14 @@ Object.assign(window.dirac, (function() { return loadLazyDirac().then(() => window.dirac.invalidateNamespaceSymbolsCache(...args)); } + function extractMacroNamespaceSymbolsAsync(...args) { + return loadLazyDirac().then(() => window.dirac.extractMacroNamespaceSymbolsAsync(...args)); + } + + function invalidateMacroNamespaceSymbolsCache(...args) { + return loadLazyDirac().then(() => window.dirac.invalidateMacroNamespaceSymbolsCache(...args)); + } + function extractNamespacesAsync(...args) { return loadLazyDirac().then(() => window.dirac.extractNamespacesAsync(...args)); } @@ -213,6 +221,8 @@ Object.assign(window.dirac, (function() { _DEBUG_COMPLETIONS: false, _DEBUG_KEYSIM: false, _DEBUG_FEEDBACK: false, + + // -- feature toggles ----------------------------------------------------------------------------------------------- hasREPL: hasFeature("enable-repl"), hasParinfer: hasFeature("enable-parinfer"), hasFriendlyLocals: hasFeature("enable-friendly-locals"), @@ -221,6 +231,8 @@ Object.assign(window.dirac, (function() { hasWelcomeMessage: hasFeature("welcome-message"), hasCleanUrls: hasFeature("clean-urls"), hasBeautifyFunctionNames: hasFeature("beautify-function-names"), + + // -- INTERFACE ----------------------------------------------------------------------------------------------------- hasFeature: hasFeature, codeAsString: codeAsString, stringEscape: stringEscape, @@ -228,19 +240,24 @@ Object.assign(window.dirac, (function() { hasCurrentContext: hasCurrentContext, evalInDefaultContext: evalInDefaultContext, hasDefaultContext: hasDefaultContext, - // note: there will be more functions added to this object dynamically by dirac.implant init code - // see externs.js for full list of avail functions - // also below functions can be lazy loaded deduplicate: deduplicate, + stableSort: stableSort, - // ---- LAZY INTERFACE ---------------------------------------------------------------------------------------------- + // -- LAZY INTERFACE ------------------------------------------------------------------------------------------------ startListeningForWorkspaceChanges: startListeningForWorkspaceChanges, stopListeningForWorkspaceChanges: stopListeningForWorkspaceChanges, extractScopeInfoFromScopeChainAsync: extractScopeInfoFromScopeChainAsync, extractNamespaceSymbolsAsync: extractNamespaceSymbolsAsync, + extractMacroNamespaceSymbolsAsync: extractMacroNamespaceSymbolsAsync, extractNamespacesAsync: extractNamespacesAsync, invalidateNamespaceSymbolsCache: invalidateNamespaceSymbolsCache, + invalidateMacroNamespaceSymbolsCache: invalidateMacroNamespaceSymbolsCache, invalidateNamespacesCache: invalidateNamespacesCache + + // ... + + // note: there will be more functions added to this object dynamically by dirac.implant init code + // see externs.js for full list of avail functions }; })()); \ No newline at end of file diff --git a/resources/unpacked/devtools/front_end/dirac_lazy/dirac_lazy.js b/resources/unpacked/devtools/front_end/dirac_lazy/dirac_lazy.js index 9d138a599e..1496c9f1cc 100644 --- a/resources/unpacked/devtools/front_end/dirac_lazy/dirac_lazy.js +++ b/resources/unpacked/devtools/front_end/dirac_lazy/dirac_lazy.js @@ -100,8 +100,6 @@ Object.assign(window.dirac, (function() { // --- helpers -------------------------------------------------------------------------------------------------------------- - var namespacesSymbolsCache = new Map(); - /** * @param {string} namespaceName * @return {function(string)} @@ -185,6 +183,8 @@ Object.assign(window.dirac, (function() { // this is to reflect dynamically updated files e.g. by Figwheel var listeningForWorkspaceChanges = false; + var namespacesSymbolsCache = new Map(); + var macroNamespacesSymbolsCache = new Map(); function invalidateNamespaceSymbolsMatchingUrl(url) { for (let namespaceName of namespacesSymbolsCache.keys()) { @@ -261,7 +261,76 @@ Object.assign(window.dirac, (function() { listeningForWorkspaceChanges = false; } -// --- namespace symbols ---------------------------------------------------------------------------------------------------- + // --- namespace names -------------------------------------------------------------------------------------------------- + + function extractNamespacesAsyncWorker() { + const workspace = WebInspector.workspace; + if (!workspace) { + console.error("unable to locate WebInspector.workspace when extracting all ClojureScript namespace names"); + return Promise.resolve([]); + } + + const uiSourceCodes = getRelevantSourceCodes(workspace); + const promises = []; + for (var i = 0; i < uiSourceCodes.length; i++) { + const uiSourceCode = uiSourceCodes[i]; + if (!uiSourceCode) { + continue; + } + const script = getScriptFromSourceCode(uiSourceCode); + if (!script) { + continue; + } + promises.push(parseNamespacesDescriptorsAsync(/** @type {!WebInspector.Script} */(script))); + } + + const concatResults = results => { + return [].concat.apply([], results); + }; + + return Promise.all(promises).then(concatResults); + } + + function prepareNamespacesCache(namespaceDescriptors) { + const result = {}; + for (let descriptor of namespaceDescriptors) { + result[descriptor.name] = descriptor; + } + return result; + } + + var extractNamespacesAsyncInFlightPromise = null; + + function extractNamespacesAsync() { + if (dirac._namespacesCache) { + return Promise.resolve(dirac._namespacesCache); + } + + // extractNamespacesAsync can take some time parsing all namespaces + // it could happen that extractNamespacesAsync() is called multiple times from code-completion code + // here we cache in-flight promise to prevent that + if (extractNamespacesAsyncInFlightPromise) { + return extractNamespacesAsyncInFlightPromise; + } + + extractNamespacesAsyncInFlightPromise = extractNamespacesAsyncWorker().then(descriptors => { + dirac._namespacesCache = prepareNamespacesCache(descriptors); + startListeningForWorkspaceChanges(); + return dirac._namespacesCache; + }); + + extractNamespacesAsyncInFlightPromise.then(result => extractNamespacesAsyncInFlightPromise = null); + return extractNamespacesAsyncInFlightPromise; + } + + function invalidateNamespacesCache() { + if (dirac._DEBUG_COMPLETIONS) { + console.log("invalidateNamespacesCache"); + } + dirac._namespacesCache = null; + } + + // --- namespace symbols ------------------------------------------------------------------------------------------------ /** * @param {!Array} uiSourceCodes @@ -361,17 +430,17 @@ Object.assign(window.dirac, (function() { if (!namespaceName) { return Promise.resolve([]); } + if (namespacesSymbolsCache.has(namespaceName)) { - return Promise.resolve(namespacesSymbolsCache.get(namespaceName)); + return namespacesSymbolsCache.get(namespaceName); } - return new Promise(resolve => { - extractNamespaceSymbolsAsyncWorker(namespaceName).then(result => { - namespacesSymbolsCache.set(namespaceName, result); - startListeningForWorkspaceChanges(); - resolve(result); - }); - }); + const promisedResult = extractNamespaceSymbolsAsyncWorker(namespaceName); + + namespacesSymbolsCache.set(namespaceName, promisedResult); + + startListeningForWorkspaceChanges(); + return promisedResult; } function invalidateNamespaceSymbolsCache(namespaceName) { @@ -381,65 +450,58 @@ Object.assign(window.dirac, (function() { namespacesSymbolsCache.delete(namespaceName); } -// --- namespace names ------------------------------------------------------------------------------------------------------ - - function extractNamespacesAsyncWorker() { - const workspace = WebInspector.workspace; - if (!workspace) { - console.error("unable to locate WebInspector.workspace when extracting all ClojureScript namespace names"); - return Promise.resolve([]); - } - - const uiSourceCodes = getRelevantSourceCodes(workspace); - const promises = []; - for (var i = 0; i < uiSourceCodes.length; i++) { - const uiSourceCode = uiSourceCodes[i]; - if (!uiSourceCode) { - continue; - } - const script = getScriptFromSourceCode(uiSourceCode); - if (!script) { - continue; + // --- macro namespaces symbols ----------------------------------------------------------------------------------------- + // + // a situation is a bit more tricky here + // we don't have source mapping to clojure land in case of macro .clj files (makes no sense) + // but thanks to our access to all existing (ns ...) forms in the project we can infer at least some information + // we can at least collect macro symbols referred to via :refer + + function extractMacroNamespaceSymbolsAsyncWorker(namespaceName) { + + // TODO: we probably want to cache this + const collectMacroSymbols = namespaceDescriptors => { + const symbols = []; + for (const descriptor of Object.values(namespaceDescriptors)) { + const refers = descriptor.macroRefers; + if (!refers) { + continue; + } + for (const symbol of Object.keys(refers)) { + const ns = refers[symbol]; + if (ns == namespaceName) { + symbols.push(symbol); + } + } } - promises.push(parseNamespacesDescriptorsAsync(/** @type {!WebInspector.Script} */(script))); - } - - const concatResults = results => { - return [].concat.apply([], results); + return dirac.deduplicate(symbols); }; - return Promise.all(promises).then(concatResults); + return dirac.extractNamespacesAsync().then(collectMacroSymbols); } - var extractNamespacesAsyncInFlightPromise = null; - - function extractNamespacesAsync() { - if (dirac._namespacesCache) { - return Promise.resolve(dirac._namespacesCache); + function extractMacroNamespaceSymbolsAsync(namespaceName) { + if (!namespaceName) { + return Promise.resolve([]); } - // extractNamespacesAsync can take some time parsing all namespaces - // it could happen that extractNamespacesAsync() is called multiple times from code-completion code - // here we cache in-flight promise to prevent that - if (extractNamespacesAsyncInFlightPromise) { - return extractNamespacesAsyncInFlightPromise; + if (macroNamespacesSymbolsCache.has(namespaceName)) { + return macroNamespacesSymbolsCache.get(namespaceName); } - extractNamespacesAsyncInFlightPromise = extractNamespacesAsyncWorker().then(result => { - dirac._namespacesCache = result; - startListeningForWorkspaceChanges(); - return result; - }); + const promisedResult = extractMacroNamespaceSymbolsAsyncWorker(namespaceName); - extractNamespacesAsyncInFlightPromise.then(result => extractNamespacesAsyncInFlightPromise = null); - return extractNamespacesAsyncInFlightPromise; + macroNamespacesSymbolsCache.set(namespaceName, promisedResult); + + startListeningForWorkspaceChanges(); + return promisedResult; } - function invalidateNamespacesCache() { + function invalidateMacroNamespaceSymbolsCache(namespaceName) { if (dirac._DEBUG_COMPLETIONS) { - console.log("invalidateNamespacesCache"); + console.log("invalidateMacroNamespaceSymbolsCache", namespaceName); } - dirac._namespacesCache = null; + macroNamespacesSymbolsCache.delete(namespaceName); } // --- exported interface --------------------------------------------------------------------------------------------------- @@ -448,12 +510,15 @@ Object.assign(window.dirac, (function() { return { _lazyLoaded: true, _namespacesSymbolsCache: namespacesSymbolsCache, + _macroNamespacesSymbolsCache: macroNamespacesSymbolsCache, _namespacesCache: null, startListeningForWorkspaceChanges: startListeningForWorkspaceChanges, stopListeningForWorkspaceChanges: stopListeningForWorkspaceChanges, extractScopeInfoFromScopeChainAsync: extractScopeInfoFromScopeChainAsync, extractNamespaceSymbolsAsync: extractNamespaceSymbolsAsync, invalidateNamespaceSymbolsCache: invalidateNamespaceSymbolsCache, + extractMacroNamespaceSymbolsAsync: extractMacroNamespaceSymbolsAsync, + invalidateMacroNamespaceSymbolsCache: invalidateMacroNamespaceSymbolsCache, extractNamespacesAsync: extractNamespacesAsync, invalidateNamespacesCache: invalidateNamespacesCache }; diff --git a/resources/unpacked/devtools/front_end/externs.js b/resources/unpacked/devtools/front_end/externs.js index 5b62004cd3..2a0854ba8a 100644 --- a/resources/unpacked/devtools/front_end/externs.js +++ b/resources/unpacked/devtools/front_end/externs.js @@ -404,7 +404,12 @@ var dirac = { */ extractNamespaceSymbolsAsync: function(namespaceName) {}, /** - * @return {!Promise>} + * @param {string} namespaceName + * @return {!Promise>} + */ + extractMacroNamespaceSymbolsAsync: function(namespaceName) {}, + /** + * @return {!Promise>} */ extractNamespacesAsync: function() {}, @@ -414,6 +419,10 @@ var dirac = { * @param {string} namespaceName */ invalidateNamespaceSymbolsCache: function (namespaceName) {}, + /** + * @param {string} namespaceName + */ + invalidateMacroNamespaceSymbolsCache: function (namespaceName) {}, invalidateNamespacesCache: function() {}, /** @@ -497,7 +506,15 @@ dirac.ScopeFrame; dirac.ScopeInfo; /** - * @typedef {{name:!string, url:!string, aliases:?Object., uses:?Object., maliases:?Object., muses:?Object.}} + * @typedef {{ + * name:!string, + * url:!string, + * namespaceAliases:?Object., + * namespaceRefers:?Object., + * macroNamespaceAliases:?Object., + * macroRefers:?Object., + * detectedMacroNamespaces:?Array. + * }} */ dirac.NamespaceDescriptor;