Skip to content

Commit 5ca8fee

Browse files
jsonifyclaudegemini-code-assist[bot]
authored
Add summarize selection context menu feature (#130)
* feat: add Summarize Selection context menu command - Add new context menu item in Noted submenu for summarizing selected text - Works in any file (not just Notes folder) - Uses VS Code LLM API (GitHub Copilot) for AI-powered summarization - Generates concise 2-3 sentence summary - Inserts summary below selection with "## Summary of Selection" header - Shows error message if Copilot is unavailable * fix: add empty line before Summary of Selection header * Update src/commands/summarizationCommands.ts Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * fix: pass cancellation token to summarizeSelection and simplify insertion - Add cancellation token parameter to summarizeSelection method - Pass token from withProgress to enable proper cancellation - Simplify insertion logic to always insert at end of selection line --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
1 parent 7e78493 commit 5ca8fee

File tree

4 files changed

+119
-3
lines changed

4 files changed

+119
-3
lines changed

package.json

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -681,6 +681,11 @@
681681
"title": "Generate Tags for Current Note",
682682
"icon": "$(sparkle)"
683683
},
684+
{
685+
"command": "noted.summarizeSelection",
686+
"title": "Summarize Selection",
687+
"icon": "$(sparkle)"
688+
},
684689
{
685690
"command": "noted.batchGenerateTags",
686691
"title": "Noted: Batch Generate Tags",
@@ -806,15 +811,20 @@
806811
"when": "editorHasSelection",
807812
"group": "1_edit@1"
808813
},
814+
{
815+
"command": "noted.summarizeSelection",
816+
"when": "editorHasSelection",
817+
"group": "2_ai@1"
818+
},
809819
{
810820
"command": "noted.summarizeCurrentNote",
811821
"when": "resourcePath =~ /Notes.*\\.(txt|md)$/",
812-
"group": "2_ai@1"
822+
"group": "2_ai@2"
813823
},
814824
{
815825
"command": "noted.generateTagsCurrentNote",
816826
"when": "resourcePath =~ /Notes.*\\.(txt|md)$/",
817-
"group": "2_ai@2"
827+
"group": "2_ai@3"
818828
}
819829
],
820830
"editor/context": [

src/commands/summarizationCommands.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,66 @@ export class SummarizationCommands {
341341
}
342342
}
343343

344+
/**
345+
* Handle summarizing selected text and inserting summary below the selection
346+
*/
347+
public async handleSummarizeSelection(): Promise<void> {
348+
const editor = vscode.window.activeTextEditor;
349+
if (!editor) {
350+
vscode.window.showErrorMessage('No editor is currently open');
351+
return;
352+
}
353+
354+
const selection = editor.selection;
355+
if (selection.isEmpty) {
356+
vscode.window.showErrorMessage('No text is selected. Please select some text to summarize.');
357+
return;
358+
}
359+
360+
const selectedText = editor.document.getText(selection);
361+
if (!selectedText.trim()) {
362+
vscode.window.showErrorMessage('Selected text is empty or contains only whitespace');
363+
return;
364+
}
365+
366+
try {
367+
await vscode.window.withProgress(
368+
{
369+
location: vscode.ProgressLocation.Notification,
370+
title: 'Summarizing selection...',
371+
cancellable: true
372+
},
373+
async (progress, token) => {
374+
if (token.isCancellationRequested) {
375+
return;
376+
}
377+
378+
const summary = await this.summarizationService.summarizeSelection(selectedText, token);
379+
380+
if (token.isCancellationRequested) {
381+
return;
382+
}
383+
384+
// Insert summary at the end of the line containing the selection's end point
385+
// to ensure consistent spacing.
386+
const endLine = editor.document.lineAt(selection.end.line);
387+
const insertPosition = endLine.range.end;
388+
389+
// Format the summary with header (extra blank line before header)
390+
const summaryText = `\n\n## Summary of Selection\n\n${summary}\n`;
391+
392+
await editor.edit(editBuilder => {
393+
editBuilder.insert(insertPosition, summaryText);
394+
});
395+
396+
vscode.window.showInformationMessage('Summary inserted below selection');
397+
}
398+
);
399+
} catch (error) {
400+
vscode.window.showErrorMessage(`Failed to generate summary: ${error instanceof Error ? error.message : String(error)}`);
401+
}
402+
}
403+
344404
// ========================================================================
345405
// Private Helper Methods
346406
// ========================================================================

src/extension.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2081,6 +2081,10 @@ export function activate(context: vscode.ExtensionContext) {
20812081
await summarizationCommands.handleSummarizeCurrentNote();
20822082
});
20832083

2084+
let summarizeSelection = vscode.commands.registerCommand('noted.summarizeSelection', async () => {
2085+
await summarizationCommands.handleSummarizeSelection();
2086+
});
2087+
20842088
let summarizeRecent = vscode.commands.registerCommand('noted.summarizeRecent', async () => {
20852089
await summarizationCommands.handleSummarizeRecent();
20862090
});
@@ -2226,7 +2230,7 @@ export function activate(context: vscode.ExtensionContext) {
22262230
showPreview, showMarkdownToolbar,
22272231
undoCommand, redoCommand, showUndoHistory, clearUndoHistory,
22282232
renameSymbol,
2229-
summarizeNote, summarizeCurrentNote, summarizeRecent, summarizeWeek, summarizeMonth, summarizeCustomRange, clearSummaryCache, summarizeSearchResults,
2233+
summarizeNote, summarizeCurrentNote, summarizeSelection, summarizeRecent, summarizeWeek, summarizeMonth, summarizeCustomRange, clearSummaryCache, summarizeSearchResults,
22302234
showSummaryHistory, compareSummaries, restoreSummaryVersion, clearSummaryHistory, showSummaryHistoryStats,
22312235
createPromptTemplate, editPromptTemplate, deletePromptTemplate, duplicatePromptTemplate, listPromptTemplates, viewTemplateVariables,
22322236
suggestTags,

src/services/summarizationService.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,4 +526,46 @@ ${note.content}
526526
}
527527
return 'GitHub Copilot';
528528
}
529+
530+
/**
531+
* Summarize selected text (concise 2-3 sentence summary)
532+
*/
533+
async summarizeSelection(selectedText: string, token: vscode.CancellationToken): Promise<string> {
534+
// Check if AI is enabled
535+
if (!this.isAIEnabled()) {
536+
throw new Error('AI summarization is disabled. Enable it in settings: noted.ai.enabled');
537+
}
538+
539+
// Check if Language Model API is available
540+
if (!(await this.isLanguageModelAvailable())) {
541+
throw new Error('Copilot is not available. Please install and enable GitHub Copilot to use AI summarization features.');
542+
}
543+
544+
// Truncate if too large
545+
const maxChars = 16000;
546+
const truncatedText = selectedText.length > maxChars
547+
? selectedText.substring(0, maxChars) + '\n\n[Text truncated for summarization]'
548+
: selectedText;
549+
550+
// Build a concise summarization prompt
551+
const prompt = `Summarize the following text in 2-3 concise sentences, capturing the main points:\n\n---\n${truncatedText}\n---\n\nProvide only the summary without any preamble or extra formatting.`;
552+
553+
// Call Language Model API
554+
try {
555+
const model = await selectAIModel();
556+
const messages = [vscode.LanguageModelChatMessage.User(prompt)];
557+
558+
const response = await model.sendRequest(messages, {}, token);
559+
560+
let summary = '';
561+
for await (const chunk of response.text) {
562+
summary += chunk;
563+
}
564+
565+
return summary.trim();
566+
567+
} catch (error) {
568+
throw new Error(`Failed to generate summary: ${error instanceof Error ? error.message : String(error)}`);
569+
}
570+
}
529571
}

0 commit comments

Comments
 (0)