mirror of
http://10.0.2.1:3031/sauer/claude-code.git
synced 2026-06-30 15:36:57 +10:00
194 lines
21 KiB
TypeScript
194 lines
21 KiB
TypeScript
|
|
import { c as _c } from "react/compiler-runtime";
|
||
|
|
/**
|
||
|
|
* Hook for LSP plugin recommendations
|
||
|
|
*
|
||
|
|
* Detects file edits and recommends LSP plugins when:
|
||
|
|
* - File extension matches an LSP plugin
|
||
|
|
* - LSP binary is already installed on the system
|
||
|
|
* - Plugin is not already installed
|
||
|
|
* - User hasn't disabled recommendations
|
||
|
|
*
|
||
|
|
* Only shows one recommendation per session.
|
||
|
|
*/
|
||
|
|
|
||
|
|
import { extname, join } from 'path';
|
||
|
|
import * as React from 'react';
|
||
|
|
import { hasShownLspRecommendationThisSession, setLspRecommendationShownThisSession } from '../bootstrap/state.js';
|
||
|
|
import { useNotifications } from '../context/notifications.js';
|
||
|
|
import { useAppState } from '../state/AppState.js';
|
||
|
|
import { saveGlobalConfig } from '../utils/config.js';
|
||
|
|
import { logForDebugging } from '../utils/debug.js';
|
||
|
|
import { logError } from '../utils/log.js';
|
||
|
|
import { addToNeverSuggest, getMatchingLspPlugins, incrementIgnoredCount } from '../utils/plugins/lspRecommendation.js';
|
||
|
|
import { cacheAndRegisterPlugin } from '../utils/plugins/pluginInstallationHelpers.js';
|
||
|
|
import { getSettingsForSource, updateSettingsForSource } from '../utils/settings/settings.js';
|
||
|
|
import { installPluginAndNotify, usePluginRecommendationBase } from './usePluginRecommendationBase.js';
|
||
|
|
|
||
|
|
// Threshold for detecting timeout vs explicit dismiss (ms)
|
||
|
|
// Menu auto-dismisses at 30s, so anything over 28s is likely timeout
|
||
|
|
const TIMEOUT_THRESHOLD_MS = 28_000;
|
||
|
|
export type LspRecommendationState = {
|
||
|
|
pluginId: string;
|
||
|
|
pluginName: string;
|
||
|
|
pluginDescription?: string;
|
||
|
|
fileExtension: string;
|
||
|
|
shownAt: number; // Timestamp for timeout detection
|
||
|
|
} | null;
|
||
|
|
type UseLspPluginRecommendationResult = {
|
||
|
|
recommendation: LspRecommendationState;
|
||
|
|
handleResponse: (response: 'yes' | 'no' | 'never' | 'disable') => void;
|
||
|
|
};
|
||
|
|
export function useLspPluginRecommendation() {
|
||
|
|
const $ = _c(12);
|
||
|
|
const trackedFiles = useAppState(_temp);
|
||
|
|
const {
|
||
|
|
addNotification
|
||
|
|
} = useNotifications();
|
||
|
|
let t0;
|
||
|
|
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||
|
|
t0 = new Set();
|
||
|
|
$[0] = t0;
|
||
|
|
} else {
|
||
|
|
t0 = $[0];
|
||
|
|
}
|
||
|
|
const checkedFilesRef = React.useRef(t0);
|
||
|
|
const {
|
||
|
|
recommendation,
|
||
|
|
clearRecommendation,
|
||
|
|
tryResolve
|
||
|
|
} = usePluginRecommendationBase();
|
||
|
|
let t1;
|
||
|
|
let t2;
|
||
|
|
if ($[1] !== trackedFiles || $[2] !== tryResolve) {
|
||
|
|
t1 = () => {
|
||
|
|
tryResolve(async () => {
|
||
|
|
if (hasShownLspRecommendationThisSession()) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
const newFiles = [];
|
||
|
|
for (const file of trackedFiles) {
|
||
|
|
if (!checkedFilesRef.current.has(file)) {
|
||
|
|
checkedFilesRef.current.add(file);
|
||
|
|
newFiles.push(file);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
for (const filePath of newFiles) {
|
||
|
|
;
|
||
|
|
try {
|
||
|
|
const matches = await getMatchingLspPlugins(filePath);
|
||
|
|
const match = matches[0];
|
||
|
|
if (match) {
|
||
|
|
logForDebugging(`[useLspPluginRecommendation] Found match: ${match.pluginName} for ${filePath}`);
|
||
|
|
setLspRecommendationShownThisSession(true);
|
||
|
|
return {
|
||
|
|
pluginId: match.pluginId,
|
||
|
|
pluginName: match.pluginName,
|
||
|
|
pluginDescription: match.description,
|
||
|
|
fileExtension: extname(filePath),
|
||
|
|
shownAt: Date.now()
|
||
|
|
};
|
||
|
|
}
|
||
|
|
} catch (t3) {
|
||
|
|
const error = t3;
|
||
|
|
logError(error);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return null;
|
||
|
|
});
|
||
|
|
};
|
||
|
|
t2 = [trackedFiles, tryResolve];
|
||
|
|
$[1] = trackedFiles;
|
||
|
|
$[2] = tryResolve;
|
||
|
|
$[3] = t1;
|
||
|
|
$[4] = t2;
|
||
|
|
} else {
|
||
|
|
t1 = $[3];
|
||
|
|
t2 = $[4];
|
||
|
|
}
|
||
|
|
React.useEffect(t1, t2);
|
||
|
|
let t3;
|
||
|
|
if ($[5] !== addNotification || $[6] !== clearRecommendation || $[7] !== recommendation) {
|
||
|
|
t3 = response => {
|
||
|
|
if (!recommendation) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const {
|
||
|
|
pluginId,
|
||
|
|
pluginName,
|
||
|
|
shownAt
|
||
|
|
} = recommendation;
|
||
|
|
logForDebugging(`[useLspPluginRecommendation] User response: ${response} for ${pluginName}`);
|
||
|
|
bb60: switch (response) {
|
||
|
|
case "yes":
|
||
|
|
{
|
||
|
|
installPluginAndNotify(pluginId, pluginName, "lsp-plugin", addNotification, async pluginData => {
|
||
|
|
logForDebugging(`[useLspPluginRecommendation] Installing plugin: ${pluginId}`);
|
||
|
|
const localSourcePath = typeof pluginData.entry.source === "string" ? join(pluginData.marketplaceInstallLocation, pluginData.entry.source) : undefined;
|
||
|
|
await cacheAndRegisterPlugin(pluginId, pluginData.entry, "user", undefined, localSourcePath);
|
||
|
|
const settings = getSettingsForSource("userSettings");
|
||
|
|
updateSettingsForSource("userSettings", {
|
||
|
|
enabledPlugins: {
|
||
|
|
...settings?.enabledPlugins,
|
||
|
|
[pluginId]: true
|
||
|
|
}
|
||
|
|
});
|
||
|
|
logForDebugging(`[useLspPluginRecommendation] Plugin installed: ${pluginId}`);
|
||
|
|
});
|
||
|
|
break bb60;
|
||
|
|
}
|
||
|
|
case "no":
|
||
|
|
{
|
||
|
|
const elapsed = Date.now() - shownAt;
|
||
|
|
if (elapsed >= TIMEOUT_THRESHOLD_MS) {
|
||
|
|
logForDebugging(`[useLspPluginRecommendation] Timeout detected (${elapsed}ms), incrementing ignored count`);
|
||
|
|
incrementIgnoredCount();
|
||
|
|
}
|
||
|
|
break bb60;
|
||
|
|
}
|
||
|
|
case "never":
|
||
|
|
{
|
||
|
|
addToNeverSuggest(pluginId);
|
||
|
|
break bb60;
|
||
|
|
}
|
||
|
|
case "disable":
|
||
|
|
{
|
||
|
|
saveGlobalConfig(_temp2);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
clearRecommendation();
|
||
|
|
};
|
||
|
|
$[5] = addNotification;
|
||
|
|
$[6] = clearRecommendation;
|
||
|
|
$[7] = recommendation;
|
||
|
|
$[8] = t3;
|
||
|
|
} else {
|
||
|
|
t3 = $[8];
|
||
|
|
}
|
||
|
|
const handleResponse = t3;
|
||
|
|
let t4;
|
||
|
|
if ($[9] !== handleResponse || $[10] !== recommendation) {
|
||
|
|
t4 = {
|
||
|
|
recommendation,
|
||
|
|
handleResponse
|
||
|
|
};
|
||
|
|
$[9] = handleResponse;
|
||
|
|
$[10] = recommendation;
|
||
|
|
$[11] = t4;
|
||
|
|
} else {
|
||
|
|
t4 = $[11];
|
||
|
|
}
|
||
|
|
return t4;
|
||
|
|
}
|
||
|
|
function _temp2(current) {
|
||
|
|
if (current.lspRecommendationDisabled) {
|
||
|
|
return current;
|
||
|
|
}
|
||
|
|
return {
|
||
|
|
...current,
|
||
|
|
lspRecommendationDisabled: true
|
||
|
|
};
|
||
|
|
}
|
||
|
|
function _temp(s) {
|
||
|
|
return s.fileHistory.trackedFiles;
|
||
|
|
}
|
||
|
|
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJleHRuYW1lIiwiam9pbiIsIlJlYWN0IiwiaGFzU2hvd25Mc3BSZWNvbW1lbmRhdGlvblRoaXNTZXNzaW9uIiwic2V0THNwUmVjb21tZW5kYXRpb25TaG93blRoaXNTZXNzaW9uIiwidXNlTm90aWZpY2F0aW9ucyIsInVzZUFwcFN0YXRlIiwic2F2ZUdsb2JhbENvbmZpZyIsImxvZ0ZvckRlYnVnZ2luZyIsImxvZ0Vycm9yIiwiYWRkVG9OZXZlclN1Z2dlc3QiLCJnZXRNYXRjaGluZ0xzcFBsdWdpbnMiLCJpbmNyZW1lbnRJZ25vcmVkQ291bnQiLCJjYWNoZUFuZFJlZ2lzdGVyUGx1Z2luIiwiZ2V0U2V0dGluZ3NGb3JTb3VyY2UiLCJ1cGRhdGVTZXR0aW5nc0ZvclNvdXJjZSIsImluc3RhbGxQbHVnaW5BbmROb3RpZnkiLCJ1c2VQbHVnaW5SZWNvbW1lbmRhdGlvbkJhc2UiLCJUSU1FT1VUX1RIUkVTSE9MRF9NUyIsIkxzcFJlY29tbWVuZGF0aW9uU3RhdGUiLCJwbHVnaW5JZCIsInBsdWdpbk5hbWUiLCJwbHVnaW5EZXNjcmlwdGlvbiIsImZpbGVFeHRlbnNpb24iLCJzaG93bkF0IiwiVXNlTHNwUGx1Z2luUmVjb21tZW5kYXRpb25SZXN1bHQiLCJyZWNvbW1lbmRhdGlvbiIsImhhbmRsZVJlc3BvbnNlIiwicmVzcG9uc2UiLCJ1c2VMc3BQbHVnaW5SZWNvbW1lbmRhdGlvbiIsIiQiLCJfYyIsInRyYWNrZWRGaWxlcyIsIl90ZW1wIiwiYWRkTm90aWZpY2F0aW9uIiwidDAiLCJTeW1ib2wiLCJmb3IiLCJTZXQiLCJjaGVja2VkRmlsZXNSZWYiLCJ1c2VSZWYiLCJjbGVhclJlY29tbWVuZGF0aW9uIiwidHJ5UmVzb2x2ZSIsInQxIiwidDIiLCJuZXdGaWxlcyIsImZpbGUiLCJjdXJyZW50IiwiaGFzIiwiYWRkIiwicHVzaCIsImZpbGVQYXRoIiwibWF0Y2hlcyIsIm1hdGNoIiwiZGVzY3JpcHRpb24iLCJEYXRlIiwibm93IiwidDMiLCJlcnJvciIsInVzZUVmZmVjdCIsImJiNjAiLCJwbHVnaW5EYXRhIiwibG9jYWxTb3VyY2VQYXRoIiwiZW50cnkiLCJzb3VyY2UiLCJtYXJrZXRwbGFjZUluc3RhbGxMb2NhdGlvbiIsInVuZGVmaW5lZCIsInNldHRpbmdzIiwiZW5hYmxlZFBsdWdpbnMiLCJlbGFwc2VkIiwiX3RlbXAyIiwidDQiLCJsc3BSZWNvbW1lbmRhdGlvbkRpc2FibGVkIiwicyIsImZpbGVIaXN0b3J5Il0sInNvdXJjZXMiOlsidXNlTHNwUGx1Z2luUmVjb21tZW5kYXRpb24udHN4Il0sInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogSG9vayBmb3IgTFNQIHBsdWdpbiByZWNvbW1lbmRhdGlvbnNcbiAqXG4gKiBEZXRlY3RzIGZpbGUgZWRpdHMgYW5kIHJlY29tbWVuZHMgTFNQIHBsdWdpbnMgd2hlbjpcbiAqIC0gRmlsZSBleHRlbnNpb24gbWF0Y2hlcyBhbiBMU1AgcGx1Z2luXG4gKiAtIExTUCBiaW5hcnkgaXMgYWxyZWFkeSBpbnN0YWxsZWQgb24gdGhlIHN5c3RlbVxuICogLSBQbHVnaW4gaXMgbm90IGFscmVhZHkgaW5zdGFsbGVkXG4gKiAtIFVzZXIgaGFzbid0IGRpc2FibGVkIHJlY29tbWVuZGF0aW9uc1xuICpcbiAqIE9ubHkgc2hvd3Mgb25lIHJlY29tbWVuZGF0aW9uIHBlciBzZXNzaW9uLlxuICovXG5cbmltcG9ydCB7IGV4dG5hbWUsIGpvaW4gfSBmcm9tICdwYXRoJ1xuaW1wb3J0ICogYXMgUmVhY3QgZnJvbSAncmVhY3QnXG5pbXBvcnQge1xuICBoYXNTaG93bkxzcFJlY29tbWVuZGF0aW9uVGhpc1Nlc3Npb24sXG4gIHNldExzcFJlY29tbWVuZGF0aW9uU2hvd25UaGlzU2Vzc2lvbixcbn0gZnJvbSAnLi4vYm9vdHN0cmFwL3N0YXRlLmpzJ1xuaW1wb3J0IHsgdXNlTm90aWZpY2F0aW9ucyB9IGZyb20gJy4uL2NvbnRleHQvbm90aWZpY2F0aW9ucy5qcydcbmltcG9ydCB7IHVzZUFwcFN0YXRlIH0gZnJvbSAnLi4vc3RhdGUvQXBwU3RhdGUuanMnXG5pbXBvcnQgeyBzYXZlR2xvYmFsQ29uZmlnIH0gZnJvbSAnLi4vdXRpbHMvY29uZmlnLmpzJ1xuaW1wb3J0IHsgbG9nRm9yRGVidWdnaW5nIH0gZnJvbSAnLi4vdXRpbHMvZGVidWcuanMnXG5pbXBvcnQgeyBsb2dFcnJvciB9IGZyb20gJy4uL3V0aWxzL2xvZy5qcydcbmltcG9ydCB7XG4gIGFkZFRvTmV2ZXJTdWdnZXN0LFxuICBnZXRNYXRjaGluZ0xzcFBsdWdpbnMsXG4gIGluY3JlbWVudElnbm9yZWRDb3VudCxcbn0gZnJvbSAnLi4vdXRpbHMvcGx1Z2lucy9sc3BSZWNvbW1lbmRhdGlvbi5qcydcbmltcG9ydCB7IGNhY2hlQW5kUmVnaXN0ZXJQbHVnaW4gfSBmcm9tICcuLi91dGlscy9wbHVnaW5zL3BsdWdpbkluc3RhbGxhdGlvbkhlbHBlcnMuanMnXG5pbXBvcnQge1xuICBnZXRTZXR0aW5nc0ZvclNvdXJjZSxcbiAgdXBkYXRlU2V0dGluZ3NGb3JTb3VyY2UsXG59IGZyb20gJy4uL3V0aWxzL3NldHRpbmdzL3NldHRpbmdzLmpzJ1xuaW1wb3J0IHtcbiAgaW5zdGFsbFBsdWdpbkFuZE5vdGlmeSxcbiAgdXNlUGx1Z2luUmVjb21tZW5kYXRpb25CYXNlLFxufSBmcm9tICcuL3VzZVBsdWdpblJlY29tbWVuZGF0aW9uQmFzZS5qcydcblxuLy8gVGhyZXNob2xkIGZvciBkZXRlY3RpbmcgdGltZW91dCB2cyBleHBsaWNpdCBkaXNtaXNzIChtcylcbi8vIE1lbnUgYXV0by1kaXNtaXNzZXMgYXQgMzBzLCBzbyBhbnl0aGluZyBvdmVyIDI4cyBpcyBsaWtlbHkgdGltZW91dFxuY29uc3QgVElNRU9VVF9USFJFU0hPTERfTVMgPSAyOF8wMDBcblxuZXhwb3J0IHR5cGUgTHNwUmVjb21tZW5kYXRpb25TdGF0ZSA9IHtcbiAgcGx1Z2luSWQ6IHN0cmluZ1xuICBwbHVnaW5OYW1lOiBzdHJpbmdcbiAgcGx1Z2luRGVzY3JpcHRpb24/OiBzdHJpbmdcbiAgZmlsZUV4dGVuc2lvbjogc3RyaW5nXG4gIHNob3duQXQ6IG51bWJlciAvLyBUaW1lc3RhbXAgZm9yIHRpbWVvdXQgZGV0ZWN0aW9uXG59IHwgbnVsbFxuXG50eXBlIFVzZUxzcFBsdWdpblJlY29tbWVuZGF0aW9uUmVzdWx0ID0ge1xuICByZWNvbW1lbmRhdGlvbjogTHNwUmVjb21tZW5kYXRpb25TdGF0ZVxuICBoYW5kbGVSZXNwb25zZTogKHJlc3BvbnNlOiAneWVzJyB8ICdubycgfCAnbmV2ZXInIHwgJ2Rpc2FibGUnKSA9PiB2b2lkXG59XG5cbmV4cG9ydCBmdW5jdGlvbiB1c2VMc3BQbHVnaW5SZWNvbW1lbmRhdGlvbigpOiBVc2VMc3BQbHVnaW5SZWNvbW1lbmRhdGlvblJlc3VsdCB7XG4
|