11import * as vscode from 'vscode' ;
2- import { renderMarkdown , getTaskListCount } from './renderer' ;
2+ import { renderMarkdown , getTaskListCount , extractCheckboxStates } from './renderer' ;
33import { CheckboxTreeDataProvider , CheckboxItem } from './checkboxTree' ;
44import { CheckboxCodeLensProvider } from './providers/checkboxCodeLensProvider' ;
55import { CheckboxHoverProvider } from './providers/checkboxHoverProvider' ;
@@ -49,6 +49,17 @@ export function activate(context: vscode.ExtensionContext) {
4949 ( ) => autoPreviewManager . toggleAutoPreview ( )
5050 ) ;
5151
52+ const toggleDebugDisposable = vscode . commands . registerCommand (
53+ 'checkboxPreview.toggleDebug' ,
54+ ( ) => {
55+ Logger . setVerbose ( ! Logger . isVerbose ( ) ) ;
56+ Logger . show ( ) ;
57+ vscode . window . showInformationMessage (
58+ `Markdown Checkbox Preview: Debug logging ${ Logger . isVerbose ( ) ? 'enabled' : 'disabled' } `
59+ ) ;
60+ }
61+ ) ;
62+
5263 const refreshTreeDisposable = vscode . commands . registerCommand ( 'checkboxTree.refresh' , ( ) => {
5364 treeDataProvider . refresh ( ) ;
5465 } ) ;
@@ -109,6 +120,7 @@ export function activate(context: vscode.ExtensionContext) {
109120 context . subscriptions . push (
110121 openPreviewDisposable ,
111122 toggleAutoPreviewDisposable ,
123+ toggleDebugDisposable ,
112124 refreshTreeDisposable ,
113125 toggleCheckboxDisposable ,
114126 navigateToHeaderDisposable ,
@@ -221,6 +233,8 @@ function openCheckboxPreview(
221233
222234 // Handle document changes with debouncing to avoid flooding webview
223235 let updateTimeout : NodeJS . Timeout | undefined ;
236+ let isTogglingCheckbox = false ;
237+
224238 const changeDisposable = vscode . workspace . onDidChangeTextDocument ( event => {
225239 if ( event . document . uri . toString ( ) === document . uri . toString ( ) ) {
226240 Logger . debug ( `Document change detected for ${ document . uri . toString ( ) } (${ event . contentChanges . length } change(s))` ) ;
@@ -230,17 +244,31 @@ function openCheckboxPreview(
230244 clearTimeout ( updateTimeout ) ;
231245 }
232246
233- // Debounce updates by 150ms to batch rapid changes
247+ // Use shorter debounce for checkbox toggles (faster feedback)
248+ const debounceTime = isTogglingCheckbox ? 50 : 150 ;
249+
250+ // Debounce updates to batch rapid changes
234251 updateTimeout = setTimeout ( ( ) => {
235252 const newContent = event . document . getText ( ) ;
236- const html = renderMarkdown ( newContent ) ;
253+
254+ if ( isTogglingCheckbox ) {
255+ // For checkbox toggles, send only checkbox states (targeted update)
256+ const checkboxStates = extractCheckboxStates ( newContent ) ;
257+ sendMessage ( {
258+ type : 'syncCheckboxes' ,
259+ checkboxes : checkboxStates
260+ } ) ;
261+ } else {
262+ // For other changes, do full rerender
263+ const html = renderMarkdown ( newContent ) ;
264+ sendMessage ( {
265+ type : 'rerender' ,
266+ html : html
267+ } ) ;
268+ }
269+
270+ // Always update progress
237271 const stats = getTaskListCount ( newContent ) ;
238-
239- sendMessage ( {
240- type : 'rerender' ,
241- html : html
242- } ) ;
243-
244272 sendMessage ( {
245273 type : 'updateProgress' ,
246274 completed : stats . completed ,
@@ -251,7 +279,10 @@ function openCheckboxPreview(
251279 if ( treeDataProvider ) {
252280 treeDataProvider . refresh ( ) ;
253281 }
254- } , 150 ) ;
282+
283+ // Reset checkbox toggle flag
284+ isTogglingCheckbox = false ;
285+ } , debounceTime ) ;
255286 }
256287 } ) ;
257288
@@ -282,14 +313,43 @@ function openCheckboxPreview(
282313 // Handle messages from the webview
283314 panel . webview . onDidReceiveMessage ( message => {
284315 Logger . debug ( `Received webview message: ${ message . type } ` ) ;
316+
317+ // Prefer editing the document currently tracked by the preview manager
318+ const previewUri = autoPreviewManager ?. getCurrentDocumentUri ( ) ;
319+
285320 switch ( message . type ) {
286321 case 'toggle' :
287322 Logger . debug ( `Toggling checkbox at line ${ message . line } ` ) ;
288- toggleCheckboxAtLine ( editor , message . line ) ;
323+ isTogglingCheckbox = true ;
324+ if ( previewUri ) {
325+ toggleCheckboxInDocumentUri ( previewUri , message . line ) ;
326+ } else {
327+ // Fallback to active editor
328+ const currentEditor = vscode . window . activeTextEditor ;
329+ if ( currentEditor ) {
330+ toggleCheckboxAtLine ( currentEditor , message . line ) ;
331+ } else {
332+ Logger . error ( 'No document available to toggle checkbox' ) ;
333+ }
334+ }
289335 break ;
290336 case 'navigate' :
291337 Logger . debug ( `Navigating to line ${ message . line } ` ) ;
292- navigateToLine ( editor , message . line ) ;
338+ // Prefer to navigate in the previewed document if available
339+ if ( previewUri ) {
340+ const doc = vscode . workspace . textDocuments . find ( d => d . uri . toString ( ) === previewUri . toString ( ) ) ;
341+ const editor = vscode . window . visibleTextEditors . find ( e => e . document === doc ) ;
342+ if ( editor ) {
343+ navigateToLine ( editor , message . line ) ;
344+ } else if ( doc ) {
345+ vscode . window . showTextDocument ( doc ) . then ( ed => navigateToLine ( ed , message . line ) ) ;
346+ }
347+ } else {
348+ const currentEditor = vscode . window . activeTextEditor ;
349+ if ( currentEditor ) {
350+ navigateToLine ( currentEditor , message . line ) ;
351+ }
352+ }
293353 break ;
294354 }
295355 } ) ;
@@ -320,6 +380,8 @@ function openCheckboxPreview(
320380function toggleCheckboxAtLine ( editor : vscode . TextEditor , lineNumber : number ) {
321381 const document = editor . document ;
322382
383+ Logger . info ( `Toggle checkbox requested: line=${ lineNumber } , documentUri=${ document . uri . toString ( ) } ` ) ;
384+
323385 if ( lineNumber >= document . lineCount ) {
324386 Logger . warn ( `Toggle requested for line ${ lineNumber } beyond document length ${ document . lineCount } ` ) ;
325387 return ;
@@ -328,30 +390,39 @@ function toggleCheckboxAtLine(editor: vscode.TextEditor, lineNumber: number) {
328390 const line = document . lineAt ( lineNumber ) ;
329391 const lineText = line . text ;
330392
393+ Logger . debug ( `Current line text: "${ lineText } "` ) ;
394+
331395 // Match different checkbox patterns
332396 let updatedText = lineText ;
333397
334398 // Handle [ ] -> [x]
335399 if ( lineText . includes ( '[ ]' ) ) {
336400 updatedText = lineText . replace ( / \[ \] / , '[x]' ) ;
401+ Logger . debug ( `Toggling unchecked -> checked` ) ;
337402 }
338403 // Handle [x] -> [ ] (both lowercase and uppercase)
339404 else if ( lineText . match ( / \[ [ x X ] \] / ) ) {
340405 updatedText = lineText . replace ( / \[ [ x X ] \] / , '[ ]' ) ;
406+ Logger . debug ( `Toggling checked -> unchecked` ) ;
407+ } else {
408+ Logger . warn ( `No checkbox pattern found in line ${ lineNumber } : "${ lineText } "` ) ;
341409 }
342410
343411 // Apply the edit if there was a change
344412 if ( updatedText !== lineText ) {
413+ Logger . info ( `Applying edit to line ${ lineNumber } : "${ lineText } " -> "${ updatedText } "` ) ;
345414 editor . edit ( editBuilder => {
346415 editBuilder . replace ( line . range , updatedText ) ;
347416 } ) . then ( success => {
348417 if ( ! success ) {
349418 vscode . window . showErrorMessage ( 'Failed to update checkbox state' ) ;
350419 Logger . error ( `Failed to apply checkbox toggle at line ${ lineNumber } ` ) ;
351420 } else {
352- Logger . debug ( ` Checkbox toggled at line ${ lineNumber } `) ;
421+ Logger . info ( `✅ Checkbox successfully toggled at line ${ lineNumber } `) ;
353422 }
354423 } ) ;
424+ } else {
425+ Logger . warn ( `No change needed for line ${ lineNumber } ` ) ;
355426 }
356427}
357428
@@ -715,6 +786,45 @@ function getWebviewContent(webview: vscode.Webview, context: vscode.ExtensionCon
715786</html>` ;
716787}
717788
789+ /**
790+ * Toggle a checkbox in a document by URI using WorkspaceEdit
791+ * Exported for testing purposes
792+ *
793+ * @param uri Document URI
794+ * @param lineNumber Zero-based line number
795+ */
796+ export async function toggleCheckboxInDocumentUri ( uri : vscode . Uri , lineNumber : number ) : Promise < boolean > {
797+ try {
798+ const doc = await vscode . workspace . openTextDocument ( uri ) ;
799+ const line = doc . lineAt ( lineNumber ) ;
800+ const lineText = line . text ;
801+
802+ let updatedText = lineText ;
803+ if ( / \[ \] / . test ( lineText ) ) {
804+ updatedText = lineText . replace ( / \[ \] / , '[x]' ) ;
805+ } else if ( / \[ [ x X ] \] / . test ( lineText ) ) {
806+ updatedText = lineText . replace ( / \[ [ x X ] \] / , '[ ]' ) ;
807+ } else {
808+ Logger . warn ( `No checkbox found at line ${ lineNumber } in ${ uri . toString ( ) } ` ) ;
809+ return false ;
810+ }
811+
812+ const edit = new vscode . WorkspaceEdit ( ) ;
813+ edit . replace ( uri , line . range , updatedText ) ;
814+ const success = await vscode . workspace . applyEdit ( edit ) ;
815+ if ( ! success ) {
816+ Logger . error ( `WorkspaceEdit failed for ${ uri . toString ( ) } at line ${ lineNumber } ` ) ;
817+ return false ;
818+ } else {
819+ Logger . info ( `Checkbox toggled in document ${ uri . toString ( ) } at line ${ lineNumber } ` ) ;
820+ return true ;
821+ }
822+ } catch ( err ) {
823+ Logger . error ( 'Failed to toggle checkbox by URI' , err ) ;
824+ return false ;
825+ }
826+ }
827+
718828export function deactivate ( ) {
719829 // Cleanup when extension is deactivated
720830}
0 commit comments