194 lines
6.0 KiB
TypeScript
194 lines
6.0 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;
|
|
}
|