chore(vscode-ide-companion): add qwen-code dependency to package files

This commit is contained in:
yiliang114
2025-11-25 13:39:07 +08:00
parent f503eb2520
commit f623bfbb34
10 changed files with 108 additions and 158 deletions

1
package-lock.json generated
View File

@@ -17358,6 +17358,7 @@
"license": "LICENSE", "license": "LICENSE",
"dependencies": { "dependencies": {
"@modelcontextprotocol/sdk": "^1.15.1", "@modelcontextprotocol/sdk": "^1.15.1",
"@qwen-code/qwen-code": "file:../cli",
"@qwen-code/qwen-code-core": "file:../core", "@qwen-code/qwen-code-core": "file:../core",
"cors": "^2.8.5", "cors": "^2.8.5",
"express": "^5.1.0", "express": "^5.1.0",

View File

@@ -63,6 +63,10 @@
{ {
"command": "qwenCode.clearAuthCache", "command": "qwenCode.clearAuthCache",
"title": "Qwen Code: Clear Authentication Cache" "title": "Qwen Code: Clear Authentication Cache"
},
{
"command": "qwenCode.login",
"title": "Qwen Code: Login"
} }
], ],
"configuration": { "configuration": {
@@ -109,6 +113,10 @@
{ {
"command": "qwen.diff.cancel", "command": "qwen.diff.cancel",
"when": "qwen.diff.isVisible" "when": "qwen.diff.isVisible"
},
{
"command": "qwenCode.login",
"when": "false"
} }
], ],
"editor/title": [ "editor/title": [
@@ -190,6 +198,7 @@
"dependencies": { "dependencies": {
"@modelcontextprotocol/sdk": "^1.15.1", "@modelcontextprotocol/sdk": "^1.15.1",
"@qwen-code/qwen-code-core": "file:../core", "@qwen-code/qwen-code-core": "file:../core",
"@qwen-code/qwen-code": "file:../cli",
"cors": "^2.8.5", "cors": "^2.8.5",
"express": "^5.1.0", "express": "^5.1.0",
"react": "^18.2.0", "react": "^18.2.0",

View File

@@ -43,7 +43,7 @@ export class WebViewProvider {
(message) => this.sendMessageToWebView(message), (message) => this.sendMessageToWebView(message),
); );
// Set login handler for /login command - force re-login // Set login handler for /login command - direct force re-login
this.messageHandler.setLoginHandler(async () => { this.messageHandler.setLoginHandler(async () => {
await this.forceReLogin(); await this.forceReLogin();
}); });
@@ -293,33 +293,13 @@ export class WebViewProvider {
}); });
} }
// Check if we have valid auth cache and auto-reconnect // Don't auto-login; user must use /login command
// Just initialize empty conversation for the UI
if (!this.agentInitialized) { if (!this.agentInitialized) {
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
const workingDir = workspaceFolder?.uri.fsPath || process.cwd();
const config = vscode.workspace.getConfiguration('qwenCode');
const openaiApiKey = config.get<string>('openaiApiKey', '');
// Use the same authMethod logic as qwenConnectionHandler
const authMethod = openaiApiKey ? 'openai' : 'qwen-oauth';
// Check if we have valid cached auth
const hasValidAuth = await this.authStateManager.hasValidAuth(
workingDir,
authMethod,
);
if (hasValidAuth) {
console.log( console.log(
'[WebViewProvider] Found valid auth cache, auto-reconnecting...', '[WebViewProvider] Agent not initialized, waiting for /login command',
);
// Auto-reconnect using cached auth
await this.initializeAgentConnection();
} else {
console.log(
'[WebViewProvider] No valid auth cache, waiting for /login command',
); );
await this.initializeEmptyConversation(); await this.initializeEmptyConversation();
}
} else { } else {
console.log( console.log(
'[WebViewProvider] Agent already initialized, reusing existing connection', '[WebViewProvider] Agent already initialized, reusing existing connection',
@@ -447,6 +427,39 @@ export class WebViewProvider {
} }
} }
/**
* Refresh connection without clearing auth cache
* Called when restoring WebView after VSCode restart
*/
async refreshConnection(): Promise<void> {
console.log('[WebViewProvider] Refresh connection requested');
// Disconnect existing connection if any
if (this.agentInitialized) {
try {
this.agentManager.disconnect();
console.log('[WebViewProvider] Existing connection disconnected');
} catch (error) {
console.log('[WebViewProvider] Error disconnecting:', error);
}
this.agentInitialized = false;
}
// Wait a moment for cleanup to complete
await new Promise((resolve) => setTimeout(resolve, 500));
// Reinitialize connection (will use cached auth if available)
try {
await this.initializeAgentConnection();
console.log(
'[WebViewProvider] Connection refresh completed successfully',
);
} catch (error) {
console.error('[WebViewProvider] Connection refresh failed:', error);
throw error;
}
}
/** /**
* Load messages from current Qwen session * Load messages from current Qwen session
* Creates a new ACP session for immediate message sending * Creates a new ACP session for immediate message sending
@@ -540,7 +553,7 @@ export class WebViewProvider {
* Restore an existing WebView panel (called during VSCode restart) * Restore an existing WebView panel (called during VSCode restart)
* This sets up the panel with all event listeners * This sets up the panel with all event listeners
*/ */
restorePanel(panel: vscode.WebviewPanel): void { async restorePanel(panel: vscode.WebviewPanel): Promise<void> {
console.log('[WebViewProvider] Restoring WebView panel'); console.log('[WebViewProvider] Restoring WebView panel');
this.panelManager.setPanel(panel); this.panelManager.setPanel(panel);
@@ -630,49 +643,25 @@ export class WebViewProvider {
console.log('[WebViewProvider] Panel restored successfully'); console.log('[WebViewProvider] Panel restored successfully');
// Check if we have valid auth cache and auto-reconnect on restore // Refresh connection on restore (will use cached auth if available)
if (!this.agentInitialized) { if (this.agentInitialized) {
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
const workingDir = workspaceFolder?.uri.fsPath || process.cwd();
const config = vscode.workspace.getConfiguration('qwenCode');
const openaiApiKey = config.get<string>('openaiApiKey', '');
// Use the same authMethod logic as qwenConnectionHandler
const authMethod = openaiApiKey ? 'openai' : 'qwen-oauth';
// Check if we have valid cached auth
this.authStateManager
.hasValidAuth(workingDir, authMethod)
.then(async (hasValidAuth) => {
if (hasValidAuth) {
console.log( console.log(
'[WebViewProvider] Found valid auth cache on restore, auto-reconnecting...', '[WebViewProvider] Agent was initialized, refreshing connection...',
);
await this.initializeAgentConnection();
} else {
console.log(
'[WebViewProvider] No valid auth cache after restore, waiting for /login command',
); );
try {
await this.refreshConnection();
console.log('[WebViewProvider] Connection refreshed successfully');
} catch (error) {
console.error('[WebViewProvider] Failed to refresh connection:', error);
// Fall back to empty conversation if refresh fails
this.agentInitialized = false;
await this.initializeEmptyConversation(); await this.initializeEmptyConversation();
} }
})
.catch((error) => {
console.error(
'[WebViewProvider] Failed to check auth cache after restore:',
error,
);
this.initializeEmptyConversation().catch(console.error);
});
} else { } else {
console.log( console.log(
'[WebViewProvider] Agent already initialized, loading current session...', '[WebViewProvider] Agent not initialized, waiting for /login command',
); );
// Reload current session messages await this.initializeEmptyConversation();
this.loadCurrentSessionMessages().catch((error) => {
console.error(
'[WebViewProvider] Failed to load session messages after restore:',
error,
);
});
} }
} }

View File

@@ -231,6 +231,18 @@ export async function activate(context: vscode.ExtensionContext) {
); );
log('Auth cache cleared by user'); log('Auth cache cleared by user');
}), }),
vscode.commands.registerCommand('qwenCode.login', async () => {
// Get the current WebViewProvider instance - must already exist
if (webViewProviders.length > 0) {
const provider = webViewProviders[webViewProviders.length - 1];
await provider.forceReLogin();
} else {
// No WebViewProvider exists, show a message to user
vscode.window.showInformationMessage(
'Please open Qwen Code chat first before logging in.',
);
}
}),
); );
ideServer = new IDEServer(log, diffManager); ideServer = new IDEServer(log, diffManager);

View File

@@ -38,7 +38,7 @@ export const ThinkingIcon: React.FC<ThinkingIconProps> = ({
{...props} {...props}
> >
<path <path
d="M10.3927 6.81824C10.3927 7.14824 10.1767 7.42424 9.91668 7.42424H9.75968L9.30068 5.39324C9.47868 5.28524 9.71068 5.22424 9.91668 5.22424C10.1607 5.22424 10.3927 5.50824 10.3927 5.83624V6.81824ZM4.90268 5.19124C4.55468 5.14224 4.22568 5.42624 4.22568 5.83624V6.81824C4.22568 7.17324 4.51068 7.42424 4.80068 7.42424H4.90268V5.19124ZM4.90268 8.62224C4.90268 8.70424 4.89068 8.77724 4.86768 8.84524L4.72268 9.34024C4.64868 9.58424 4.45768 9.77224 4.22568 9.84624V8.62224C4.22568 8.25824 4.51068 8.01824 4.80068 8.01824H4.90268V8.62224ZM4.78068 10.4622C4.71468 10.6532 4.60968 10.8202 4.45768 10.9572L4.05568 11.3252C3.63768 11.7132 3.00268 11.6392 2.66968 11.1692C2.35768 10.7292 2.45668 10.1212 2.89268 9.79624L3.16868 9.59424C3.36768 9.44724 3.64068 9.51324 3.76268 9.74424L4.14268 10.4622H4.78068ZM5.50268 8.62224C5.50268 8.89724 5.70768 9.08624 5.96068 9.10124H6.15568V10.9802C6.15568 11.3402 5.88268 11.6132 5.56068 11.6132H5.02068C4.68868 11.6132 4.42668 11.3302 4.42668 10.9802V9.85824C4.42668 9.72024 4.55568 9.65324 4.68568 9.70524C4.93568 9.80324 5.17568 9.77924 5.36468 9.72324C5.43568 9.70224 5.50268 9.63324 5.50268 9.54824V8.62224ZM5.50268 7.42424H5.80868H6.07968V8.01824H5.80868C5.62068 8.01824 5.50268 8.25824 5.50268 8.62224V7.42424ZM6.72668 4.93024C6.60368 4.93024 6.49468 4.98624 6.42568 5.08124L6.07968 5.55924V4.79224C6.07968 4.60024 6.14768 4.41824 6.25568 4.26724C6.36368 4.11624 6.53468 4.00724 6.72668 4.00724H8.19668C8.41568 4.00724 8.60068 4.14024 8.69768 4.32424L9.08968 5.07524H9.91668C10.5367 5.07524 10.9927 5.52224 10.9927 6.08724V6.56924C10.9927 7.13824 10.5367 7.57724 9.91668 7.57724H9.60568L9.88968 8.80224C9.95068 9.06924 9.78268 9.33324 9.50668 9.37124C9.46268 9.37724 9.41668 9.37724 9.37168 9.37124L9.23368 9.34524L8.72668 11.2652C8.61668 11.6692 8.24568 11.9232 7.80868 11.9232C7.59668 11.9232 7.39468 11.8462 7.24068 11.7082L7.14068 11.6132H7.69168V10.0562H7.07168C6.79468 10.0562 6.56968 9.81524 6.56968 9.51424V9.10024L6.78468 8.77724C6.84768 8.68324 6.87968 8.57824 6.87968 8.46824C6.87968 8.24324 6.75268 8.01824 6.55768 8.01824H6.42668V5.89024L7.27568 5.18624C7.47468 5.02524 7.56068 4.98924 7.56068 4.93024C7.56068 4.86724 7.40568 4.78724 7.20668 4.85024L6.72668 5.00524V4.93024ZM8.38268 8.01824H8.73068L9.01568 9.24624L8.30868 9.09724L8.38268 8.72824V8.01824ZM8.11668 10.0552H8.00968V11.1032L8.26668 10.1322L8.11668 10.0552ZM9.07368 10.2272L8.52468 11.7662C8.47068 11.9092 8.34668 12.0082 8.20168 12.0362C8.14268 12.0482 8.08168 12.0482 8.02068 12.0362C7.86568 12.0062 7.73068 11.9042 7.66968 11.7552C7.61268 11.6142 7.58268 11.5622 7.55268 11.4912L7.41168 11.1662L7.36468 10.7362C7.35668 10.6562 7.40068 10.5822 7.47368 10.5482C7.56368 10.5082 7.61568 10.4402 7.64068 10.3502L7.69168 10.0562H8.00968L8.68268 10.4422L9.07368 10.2272ZM7.69168 7.27024V5.86524L7.62968 5.91624C7.43768 6.07324 7.15468 6.08424 6.95168 5.94724L6.87968 5.89724V7.42424H8.21368L8.00968 6.53424C7.96768 6.35324 8.06568 6.17024 8.22568 6.10524C8.27368 6.08724 8.32168 6.08524 8.37068 6.09524L9.02868 6.22224C9.15768 6.24724 9.22668 6.39524 9.17268 6.52424L9.09668 6.71024C9.08368 6.74324 9.06368 6.77124 9.04068 6.79624L8.21268 7.57724L7.95468 7.27024H7.69168ZM8.00968 9.26224C7.94168 9.30024 7.88368 9.34024 7.83068 9.38624C7.74368 9.46024 7.66768 9.54724 7.60968 9.65224L7.54668 9.76324C7.50868 9.83224 7.48468 9.90924 7.47468 9.99124L7.47268 10.0032V10.0562H6.72668V9.51124C6.72668 9.41924 6.77068 9.33424 6.84668 9.28024L7.05768 9.13024C7.12568 9.08224 7.16268 9.00424 7.16268 8.92024V8.01824H7.83068V8.77724H8.07368V8.81624C8.07368 8.94124 8.05168 9.06024 8.00968 9.17324V9.26224ZM6.72668 8.57324C6.72668 8.57024 6.72668 8.56524 6.72668 8.56224V8.01824H6.87968V8.46824C6.87968 8.50624 6.87568 8.54324 6.86868 8.57924L6.72668 8.57324Z" d="M8.00293 1.11523L8.35059 1.12402H8.35352C11.9915 1.30834 14.8848 4.31624 14.8848 8C14.8848 11.8025 11.8025 14.8848 8 14.8848C4.19752 14.8848 1.11523 11.8025 1.11523 8C1.11523 7.67691 1.37711 7.41504 1.7002 7.41504C2.02319 7.41514 2.28516 7.67698 2.28516 8C2.28516 11.1563 4.84369 13.7148 8 13.7148C11.1563 13.7148 13.7148 11.1563 13.7148 8C13.7148 4.94263 11.3141 2.4464 8.29492 2.29297V2.29199L7.99609 2.28516H7.9873V2.28418L7.89648 2.27539L7.88281 2.27441V2.27344C7.61596 2.21897 7.41513 1.98293 7.41504 1.7002C7.41504 1.37711 7.67691 1.11523 8 1.11523H8.00293ZM8 3.81543C8.32309 3.81543 8.58496 4.0773 8.58496 4.40039V7.6377L10.9619 8.82715C11.2505 8.97169 11.3678 9.32256 11.2236 9.61133C11.0972 9.86425 10.8117 9.98544 10.5488 9.91504L10.5352 9.91211V9.91016L10.4502 9.87891L10.4385 9.87402V9.87305L7.73828 8.52344C7.54007 8.42433 7.41504 8.22155 7.41504 8V4.40039C7.41504 4.0773 7.67691 3.81543 8 3.81543ZM2.44336 5.12695C2.77573 5.19517 3.02597 5.48929 3.02637 5.8418C3.02637 6.19456 2.7761 6.49022 2.44336 6.55859L2.2959 6.57324C1.89241 6.57324 1.56543 6.24529 1.56543 5.8418C1.56588 5.43853 1.89284 5.1123 2.2959 5.1123L2.44336 5.12695ZM3.46094 2.72949C3.86418 2.72984 4.19017 3.05712 4.19043 3.45996V3.46094C4.19009 3.86393 3.86392 4.19008 3.46094 4.19043H3.45996C3.05712 4.19017 2.72983 3.86419 2.72949 3.46094V3.45996C2.72976 3.05686 3.05686 2.72976 3.45996 2.72949H3.46094ZM5.98926 1.58008C6.32235 1.64818 6.57324 1.94276 6.57324 2.2959L6.55859 2.44336C6.49022 2.7761 6.19456 3.02637 5.8418 3.02637C5.43884 3.02591 5.11251 2.69895 5.1123 2.2959L5.12695 2.14844C5.19504 1.81591 5.48906 1.56583 5.8418 1.56543L5.98926 1.58008Z"
strokeWidth="0.27" strokeWidth="0.27"
style={{ style={{
stroke: enabled stroke: enabled

View File

@@ -13,7 +13,7 @@ interface WaitingMessageProps {
export const WaitingMessage: React.FC<WaitingMessageProps> = ({ export const WaitingMessage: React.FC<WaitingMessageProps> = ({
loadingMessage, loadingMessage,
}) => ( }) => (
<div className="flex gap-0 items-start text-left py-2 flex-col relative opacity-85 animate-[fadeIn_0.2s_ease-in]"> <div className="flex gap-0 items-start text-left py-2 flex-col opacity-85 animate-[fadeIn_0.2s_ease-in]">
<div className="bg-transparent border-0 py-2 flex items-center gap-2"> <div className="bg-transparent border-0 py-2 flex items-center gap-2">
<span className="inline-flex items-center gap-1 mr-0"> <span className="inline-flex items-center gap-1 mr-0">
<span className="inline-block w-1.5 h-1.5 bg-[var(--app-secondary-foreground)] rounded-full mr-0 opacity-60 animate-[typingPulse_1.4s_infinite_ease-in-out] [animation-delay:0s]"></span> <span className="inline-block w-1.5 h-1.5 bg-[var(--app-secondary-foreground)] rounded-full mr-0 opacity-60 animate-[typingPulse_1.4s_infinite_ease-in-out] [animation-delay:0s]"></span>

View File

@@ -47,14 +47,14 @@ export class AuthMessageHandler extends BaseMessageHandler {
try { try {
console.log('[AuthMessageHandler] Login requested'); console.log('[AuthMessageHandler] Login requested');
// Direct login without additional confirmation
if (this.loginHandler) { if (this.loginHandler) {
await this.loginHandler(); await this.loginHandler();
} else { } else {
// Fallback: show message and use command
vscode.window.showInformationMessage( vscode.window.showInformationMessage(
'Please wait while we connect to Qwen Code...', 'Please wait while we connect to Qwen Code...',
); );
// Fallback: trigger WebViewProvider's forceReLogin
await vscode.commands.executeCommand('qwenCode.login'); await vscode.commands.executeCommand('qwenCode.login');
} }
} catch (error) { } catch (error) {

View File

@@ -150,6 +150,13 @@ export class MessageRouter {
*/ */
setLoginHandler(handler: () => Promise<void>): void { setLoginHandler(handler: () => Promise<void>): void {
this.authHandler.setLoginHandler(handler); this.authHandler.setLoginHandler(handler);
// Also set login handler for SessionMessageHandler
if (
this.sessionHandler &&
typeof this.sessionHandler.setLoginHandler === 'function'
) {
this.sessionHandler.setLoginHandler(handler);
}
} }
/** /**

View File

@@ -15,6 +15,7 @@ import type { ChatMessage } from '../../agents/qwenAgentManager.js';
export class SessionMessageHandler extends BaseMessageHandler { export class SessionMessageHandler extends BaseMessageHandler {
private currentStreamContent = ''; private currentStreamContent = '';
private isSavingCheckpoint = false; private isSavingCheckpoint = false;
private loginHandler: (() => Promise<void>) | null = null;
canHandle(messageType: string): boolean { canHandle(messageType: string): boolean {
return [ return [
@@ -27,6 +28,13 @@ export class SessionMessageHandler extends BaseMessageHandler {
].includes(messageType); ].includes(messageType);
} }
/**
* Set login handler
*/
setLoginHandler(handler: () => Promise<void>): void {
this.loginHandler = handler;
}
async handle(message: { type: string; data?: unknown }): Promise<void> { async handle(message: { type: string; data?: unknown }): Promise<void> {
const data = message.data as Record<string, unknown> | undefined; const data = message.data as Record<string, unknown> | undefined;
@@ -231,13 +239,23 @@ export class SessionMessageHandler extends BaseMessageHandler {
if (!this.agentManager.isConnected) { if (!this.agentManager.isConnected) {
console.warn('[SessionMessageHandler] Agent not connected'); console.warn('[SessionMessageHandler] Agent not connected');
// Show non-modal notification with Login button
const result = await vscode.window.showWarningMessage( const result = await vscode.window.showWarningMessage(
'You need to login first to use Qwen Code.', 'You need to login first to use Qwen Code.',
'Login Now', 'Login Now',
); );
if (result === 'Login Now') { if (result === 'Login Now') {
vscode.commands.executeCommand('qwenCode.login'); // Use login handler directly
if (this.loginHandler) {
await this.loginHandler();
} else {
// Fallback to command
vscode.window.showInformationMessage(
'Please wait while we connect to Qwen Code...',
);
await vscode.commands.executeCommand('qwenCode.login');
}
} }
return; return;
} }

View File

@@ -109,36 +109,12 @@ export function useCompletionTrigger(
const handleInput = async () => { const handleInput = async () => {
const text = inputElement.textContent || ''; const text = inputElement.textContent || '';
const selection = window.getSelection(); const selection = window.getSelection();
console.log(
'[useCompletionTrigger] handleInput - text:',
JSON.stringify(text),
'length:',
text.length,
);
if (!selection || selection.rangeCount === 0) { if (!selection || selection.rangeCount === 0) {
console.log('[useCompletionTrigger] No selection or rangeCount === 0'); console.log('[useCompletionTrigger] No selection or rangeCount === 0');
return; return;
} }
const range = selection.getRangeAt(0); const range = selection.getRangeAt(0);
console.log(
'[useCompletionTrigger] range.startContainer:',
range.startContainer,
'startOffset:',
range.startOffset,
);
console.log(
'[useCompletionTrigger] startContainer === inputElement:',
range.startContainer === inputElement,
);
console.log(
'[useCompletionTrigger] startContainer.nodeType:',
range.startContainer.nodeType,
'TEXT_NODE:',
Node.TEXT_NODE,
);
// Get cursor position more reliably // Get cursor position more reliably
// For contentEditable, we need to calculate the actual text offset // For contentEditable, we need to calculate the actual text offset
@@ -157,14 +133,6 @@ export function useCompletionTrigger(
offset += inputElement.childNodes[i].textContent?.length || 0; offset += inputElement.childNodes[i].textContent?.length || 0;
} }
cursorPosition = offset || text.length; cursorPosition = offset || text.length;
console.log(
'[useCompletionTrigger] Container mode - childIndex:',
childIndex,
'offset:',
offset,
'cursorPosition:',
cursorPosition,
);
} else if (range.startContainer.nodeType === Node.TEXT_NODE) { } else if (range.startContainer.nodeType === Node.TEXT_NODE) {
// Cursor is in a text node - calculate offset from start of input // Cursor is in a text node - calculate offset from start of input
const walker = document.createTreeWalker( const walker = document.createTreeWalker(
@@ -187,40 +155,17 @@ export function useCompletionTrigger(
} }
// If we found the node, use the calculated offset; otherwise use text length // If we found the node, use the calculated offset; otherwise use text length
cursorPosition = found ? offset : text.length; cursorPosition = found ? offset : text.length;
console.log(
'[useCompletionTrigger] Text node mode - found:',
found,
'offset:',
offset,
'cursorPosition:',
cursorPosition,
);
} }
// Find trigger character before cursor // Find trigger character before cursor
// Use text length if cursorPosition is 0 but we have text (edge case for first character) // Use text length if cursorPosition is 0 but we have text (edge case for first character)
const effectiveCursorPosition = const effectiveCursorPosition =
cursorPosition === 0 && text.length > 0 ? text.length : cursorPosition; cursorPosition === 0 && text.length > 0 ? text.length : cursorPosition;
console.log(
'[useCompletionTrigger] cursorPosition:',
cursorPosition,
'effectiveCursorPosition:',
effectiveCursorPosition,
);
const textBeforeCursor = text.substring(0, effectiveCursorPosition); const textBeforeCursor = text.substring(0, effectiveCursorPosition);
const lastAtMatch = textBeforeCursor.lastIndexOf('@'); const lastAtMatch = textBeforeCursor.lastIndexOf('@');
const lastSlashMatch = textBeforeCursor.lastIndexOf('/'); const lastSlashMatch = textBeforeCursor.lastIndexOf('/');
console.log(
'[useCompletionTrigger] textBeforeCursor:',
JSON.stringify(textBeforeCursor),
'lastAtMatch:',
lastAtMatch,
'lastSlashMatch:',
lastSlashMatch,
);
// Check if we're in a trigger context // Check if we're in a trigger context
let triggerPos = -1; let triggerPos = -1;
let triggerChar: '@' | '/' | null = null; let triggerChar: '@' | '/' | null = null;
@@ -233,46 +178,19 @@ export function useCompletionTrigger(
triggerChar = '/'; triggerChar = '/';
} }
console.log(
'[useCompletionTrigger] triggerPos:',
triggerPos,
'triggerChar:',
triggerChar,
);
// Check if trigger is at word boundary (start of line or after space) // Check if trigger is at word boundary (start of line or after space)
if (triggerPos >= 0 && triggerChar) { if (triggerPos >= 0 && triggerChar) {
const charBefore = triggerPos > 0 ? text[triggerPos - 1] : ' '; const charBefore = triggerPos > 0 ? text[triggerPos - 1] : ' ';
const isValidTrigger = const isValidTrigger =
charBefore === ' ' || charBefore === '\n' || triggerPos === 0; charBefore === ' ' || charBefore === '\n' || triggerPos === 0;
console.log(
'[useCompletionTrigger] charBefore:',
JSON.stringify(charBefore),
'isValidTrigger:',
isValidTrigger,
);
if (isValidTrigger) { if (isValidTrigger) {
const query = text.substring(triggerPos + 1, effectiveCursorPosition); const query = text.substring(triggerPos + 1, effectiveCursorPosition);
console.log(
'[useCompletionTrigger] query:',
JSON.stringify(query),
'hasSpace:',
query.includes(' '),
'hasNewline:',
query.includes('\n'),
);
// Only show if query doesn't contain spaces (still typing the reference) // Only show if query doesn't contain spaces (still typing the reference)
if (!query.includes(' ') && !query.includes('\n')) { if (!query.includes(' ') && !query.includes('\n')) {
// Get precise cursor position for menu // Get precise cursor position for menu
const cursorPos = getCursorPosition(); const cursorPos = getCursorPosition();
console.log(
'[useCompletionTrigger] Opening completion - cursorPos:',
cursorPos,
);
if (cursorPos) { if (cursorPos) {
await openCompletion(triggerChar, query, cursorPos); await openCompletion(triggerChar, query, cursorPos);
return; return;
@@ -282,10 +200,6 @@ export function useCompletionTrigger(
} }
// Close if no valid trigger // Close if no valid trigger
console.log(
'[useCompletionTrigger] No valid trigger, state.isOpen:',
state.isOpen,
);
if (state.isOpen) { if (state.isOpen) {
closeCompletion(); closeCompletion();
} }