Repository navigation
Expand file tree
/
Copy pathrenderer.js
More file actions
118 lines (106 loc) · 33.7 KB
/
Copy pathrenderer.js
File metadata and controls
118 lines (106 loc) · 33.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
let root=null,editor=null,current=null,models=new Map();const $=x=>document.getElementById(x),name=p=>p.split(/[\\/]/).pop(),lang=p=>p.endsWith('.py')?'python':'plaintext';function setStatus(x){$('status').textContent=x}function renderProblems(m,p){$('problems').innerHTML='';$('problemCount').textContent=m.length+' problem(s)';for(let x of m){let d=document.createElement('div');d.className='problem error';d.textContent=x.message;d.onclick=()=>openFile(p,x.startLineNumber);$('problems').appendChild(d)}}
let sc=document.createElement('script');sc.src='./node_modules/monaco-editor/min/vs/loader.js';sc.onload=()=>{require.config({paths:{vs:'./node_modules/monaco-editor/min/vs'}});require(['vs/editor/editor.main'],()=>{editor=monaco.editor.create($('editor'),{theme:'vs-dark',automaticLayout:true,minimap:{enabled:true}})})};document.body.appendChild(sc);
function tree(ns,e,d=0){for(let n of ns){let x=document.createElement('div');x.className='node';x.style.paddingLeft=(6+d*12)+'px';x.textContent=(n.dir?'▾ ':'')+n.name;e.appendChild(x);if(n.dir)tree(n.children,e,d+1);else{x.onclick=()=>openFile(n.path);x.oncontextmenu=e=>{e.preventDefault();api.fileMenu(n.path)}}}}async function loadFolder(t){root=t.root;$('workspace').textContent=root;$('tree').innerHTML='';tree(t.children,$('tree'));startTerminal()}
async function openFile(p,line){if(!models.has(p)){let r=await api.read(p);models.set(p,{model:monaco.editor.createModel(r.content,lang(p)),dirty:false});if(lang(p)==='python'&&lspProvidersReady)openLspDoc(p,models.get(p).model)}current=p;editor.setModel(models.get(p).model);$('lang').textContent=lang(p);let bc=document.getElementById('breadcrumbText');if(bc)bc.textContent=(root&&p.startsWith(root)?p.slice(root.length+1):p).replaceAll('\\',' › ').replaceAll('/',' › ');if(line)editor.setPosition({lineNumber:line,column:1})}$('open').onclick=async()=>{let t=await api.openFolder();if(t)loadFolder(t)};$('save').onclick=()=>current&&api.write(current,editor.getValue());
document.querySelectorAll('nav button').forEach(b=>b.onclick=()=>{document.querySelectorAll('.view').forEach(x=>x.classList.add('hidden'));$(b.dataset.view+'View').classList.remove('hidden')});function startTerminal(){let t=new Terminal({theme:{background:'#111'}});t.open($('terminalBox'));t.onData(api.termInput);api.onTerm(x=>t.write(x));api.termStart(root)}
let lspVersions=new Map(),lspDiagnostics=new Map(),lspChangeTimers=new Map(),lspProvidersReady=false;
const fileUri=p=>'file:///'+p.replace(/\\/g,'/').replace(/^\//,'');
const uriPath=u=>decodeURIComponent(u.replace(/^file:\/\/\//,'')).replace(/^\/([A-Za-z]:)/,'$1').replace(/\//g,navigator.platform.startsWith('Win')?'\\':'/');
const lspPos=p=>({line:p.lineNumber-1,character:p.column-1});
const monacoRange=r=>new monaco.Range(r.start.line+1,r.start.character+1,r.end.line+1,r.end.character+1);
function markup(x){if(!x)return[];if(typeof x==='string')return[{value:x}];if(Array.isArray(x))return x.map(y=>({value:typeof y==='string'?y:(y.value||'')}));return[{value:x.value||''}]}
function registerLspProviders(){if(lspProvidersReady)return;lspProvidersReady=true;
monaco.languages.registerCompletionItemProvider('python',{triggerCharacters:['.','(','[','"',"'"],provideCompletionItems:async(m,p)=>{if(!current)return{suggestions:[]};try{let r=await api.lspRequest('textDocument/completion',{textDocument:{uri:fileUri(current)},position:lspPos(p),context:{triggerKind:1}}),arr=Array.isArray(r)?r:(r?.items||[]);return{suggestions:arr.map(x=>({label:typeof x.label==='string'?x.label:x.label.label,kind:x.kind||monaco.languages.CompletionItemKind.Text,detail:x.detail,documentation:markup(x.documentation)[0],insertText:x.insertText||x.textEdit?.newText||(typeof x.label==='string'?x.label:x.label.label),insertTextRules:x.insertTextFormat===2?monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet:0,range:x.textEdit?.range?monacoRange(x.textEdit.range):undefined,sortText:x.sortText,filterText:x.filterText}))}}catch{return{suggestions:[]}}}});
monaco.languages.registerHoverProvider('python',{provideHover:async(m,p)=>{try{let r=await api.lspRequest('textDocument/hover',{textDocument:{uri:fileUri(current)},position:lspPos(p)});return r?{range:r.range?monacoRange(r.range):undefined,contents:markup(r.contents)}:null}catch{return null}}});
monaco.languages.registerDefinitionProvider('python',{provideDefinition:async(m,p)=>{try{let r=await api.lspRequest('textDocument/definition',{textDocument:{uri:fileUri(current)},position:lspPos(p)}),a=Array.isArray(r)?r:[r];return a.filter(Boolean).map(x=>({uri:monaco.Uri.file(uriPath(x.uri||x.targetUri)),range:monacoRange(x.range||x.targetSelectionRange)}))}catch{return[]}}});
monaco.languages.registerReferenceProvider('python',{provideReferences:async(m,p,c)=>{try{let r=await api.lspRequest('textDocument/references',{textDocument:{uri:fileUri(current)},position:lspPos(p),context:{includeDeclaration:c.includeDeclaration}});return(r||[]).map(x=>({uri:monaco.Uri.file(uriPath(x.uri)),range:monacoRange(x.range)}))}catch{return[]}}});
monaco.languages.registerRenameProvider('python',{provideRenameEdits:async(m,p,n)=>{try{let r=await api.lspRequest('textDocument/rename',{textDocument:{uri:fileUri(current)},position:lspPos(p),newName:n});return workspaceEdit(r)}catch(e){return{edits:[],rejectReason:e.message}}},resolveRenameLocation:async(m,p)=>{try{let r=await api.lspRequest('textDocument/prepareRename',{textDocument:{uri:fileUri(current)},position:lspPos(p)});if(!r)return{range:new monaco.Range(p.lineNumber,p.column,p.lineNumber,p.column),text:''};let q=r.range||r;return{range:monacoRange(q),text:r.placeholder||m.getValueInRange(monacoRange(q))}}catch{return null}}});
monaco.languages.registerCodeActionProvider('python',{provideCodeActions:async(m,range,ctx)=>{try{let diagnostics=(ctx.markers||[]).map(z=>({range:{start:{line:z.startLineNumber-1,character:z.startColumn-1},end:{line:z.endLineNumber-1,character:z.endColumn-1}},severity:z.severity===8?1:2,message:z.message,source:z.source,code:z.code}));let r=await api.lspRequest('textDocument/codeAction',{textDocument:{uri:fileUri(current)},range:{start:lspPos(range.getStartPosition()),end:lspPos(range.getEndPosition())},context:{diagnostics}});return{actions:(r||[]).filter(x=>x.edit).map(x=>({title:x.title,kind:x.kind,edit:workspaceEdit(x.edit),isPreferred:x.isPreferred}))}}catch{return{actions:[]}}}});
}
function workspaceEdit(w){let edits=[];if(w?.changes)for(let[u,es]of Object.entries(w.changes))for(let e of es)edits.push({resource:monaco.Uri.file(uriPath(u)),textEdit:{range:monacoRange(e.range),text:e.newText},versionId:undefined});if(w?.documentChanges)for(let d of w.documentChanges)if(d.edits)for(let e of d.edits)edits.push({resource:monaco.Uri.file(uriPath(d.textDocument.uri)),textEdit:{range:monacoRange(e.range),text:e.newText},versionId:undefined});return{edits}}
async function startLanguageServer(){if(!root)return;$('lspStatus').textContent='LSP: starting';let r=await api.lspStart(root);$('lspStatus').textContent=r?.error?'LSP: error':'LSP: ready';if(!r?.error){registerLspProviders();for(let[p,x]of models)if(lang(p)==='python')openLspDoc(p,x.model)}}
function openLspDoc(p,m){let v=lspVersions.get(p)||1;lspVersions.set(p,v);api.lspOpen(p,m.getValue(),v)}
function syncLspDoc(p,m){let v=(lspVersions.get(p)||1)+1;lspVersions.set(p,v);clearTimeout(lspChangeTimers.get(p));lspChangeTimers.set(p,setTimeout(()=>api.lspChange(p,m.getValue(),v),250))}
api.onLspDiagnostics(x=>{let p=uriPath(x.uri),markers=(x.diagnostics||[]).map(d=>({severity:d.severity===1?monaco.MarkerSeverity.Error:d.severity===2?monaco.MarkerSeverity.Warning:monaco.MarkerSeverity.Info,message:d.message,source:d.source||'Pyright',code:typeof d.code==='object'?d.code.value:d.code,startLineNumber:d.range.start.line+1,startColumn:d.range.start.character+1,endLineNumber:d.range.end.line+1,endColumn:d.range.end.character+1}));lspDiagnostics.set(p,markers);let entry=models.get(p);if(entry)monaco.editor.setModelMarkers(entry.model,'pyright-lsp',markers);if(p===current)renderProblems(markers,p)});api.onLspStatus(x=>$('lspStatus').textContent='LSP: '+x);
async function refreshOutline(){if(!current||lang(current)!=='python')return;let r=await api.lspRequest('textDocument/documentSymbol',{textDocument:{uri:fileUri(current)}}).catch(()=>[]),e=$('outline');e.innerHTML='';function add(a,depth=0){for(let x of a||[]){let d=document.createElement('div');d.className='symbolItem';d.style.paddingLeft=(8+depth*12)+'px';d.textContent=x.name;d.onclick=()=>{let q=x.selectionRange||x.range;editor.setPosition({lineNumber:q.start.line+1,column:q.start.character+1});editor.revealLineInCenter(q.start.line+1);editor.focus()};e.appendChild(d);if(x.children)add(x.children,depth+1)}}add(r)}$('outlineRefresh').onclick=refreshOutline;
// v0.5 IntelliSense foundation
function registerIntelliSense(){}function validateCurrent(){}function documentSymbols(){}$('symbols').onclick=documentSymbols;
function activateView(v){let b=document.querySelector(`[data-view="${v}"]`);if(b)b.click()}
function editorAction(id){editor&&editor.getAction(id)?.run()}
api.onMenu(async({command,arg})=>{const actions={
openFolder:()=>document.getElementById('open')?.click(),newFile:()=>document.getElementById('newFile')?.click(),newFolder:()=>document.getElementById('newFolder')?.click(),save:()=>document.getElementById('save')?.click(),saveAll:()=>typeof saveAll==='function'&&saveAll(),closeEditor:()=>current&&typeof closeFile==='function'&&closeFile(current),find:()=>editorAction('actions.find'),replace:()=>editorAction('editor.action.startFindReplaceAction'),projectSearch:()=>activateView('search'),format:()=>editorAction('editor.action.formatDocument'),comment:()=>editorAction('editor.action.commentLine'),selectAll:()=>editorAction('editor.action.selectAll'),cursorAbove:()=>editorAction('editor.action.insertCursorAbove'),cursorBelow:()=>editorAction('editor.action.insertCursorBelow'),duplicateLine:()=>editorAction('editor.action.copyLinesDownAction'),palette:()=>typeof openPalette==='function'&&openPalette(),view:()=>activateView(arg),terminal:()=>typeof showBottom==='function'?showBottom('terminalBox'):null,toggleSidebar:()=>document.querySelector('aside').classList.toggle('hidden'),togglePanel:()=>{let b=document.getElementById('bottom');b.style.display=b.style.display==='none'?'block':'none'},goLine:()=>typeof goToLine==='function'&&goToLine(),symbols:()=>typeof projectSymbols==='function'?projectSymbols():documentSymbols(),definition:()=>editorAction('editor.action.revealDefinition'),references:()=>editorAction('editor.action.goToReferences'),rename:()=>editorAction('editor.action.rename'),run:()=>document.getElementById('runBtn')?.click(),stop:()=>document.getElementById('stopBtn')?.click(),checkProject:()=>document.getElementById('checkProject')?.click(),checkPython:()=>document.getElementById('checkPython')?.click(),checkTS:()=>document.getElementById('checkTS')?.click(),newTerminal:()=>typeof startTerminal==='function'&&startTerminal(),restartTerminal:()=>typeof startTerminal==='function'&&startTerminal(),clearTerminal:()=>term&&term.clear(),closePanel:()=>{document.getElementById('bottom').style.display='none'},recent:()=>document.getElementById('welcome')?.classList.remove('hidden'),openPath:()=>openFile(arg),renamePath:()=>{},deletePath:()=>{},shortcuts:()=>alert('Ctrl+Shift+P Command Palette\nCtrl+P Go to File\nCtrl+Shift+O Symbols\nF2 Rename\nF12 Definition\nCtrl+` Terminal')};if(actions[command])await actions[command]()});
document.addEventListener('contextmenu',e=>{if(e.target.closest('#editor')){e.preventDefault();api.editorMenu()}});
document.getElementById('navSettings')?.addEventListener('click',()=>document.getElementById('settings')?.click());
// v0.8.1 theme and welcome dashboard
function applyPeaBoxTheme(theme){let light=theme==='light';document.body.classList.toggle('light',light);let b=document.getElementById('themeToggle');if(b){b.textContent=light?'☾':'☀';b.title=light?'Switch to dark theme':'Switch to light theme'}if(editor&&window.monaco)monaco.editor.setTheme(light?'vs':'vs-dark');if(state){state.settings=state.settings||{};state.settings.theme=light?'light':'dark';persist?.()}}
function renderRecentProjects(){let box=document.getElementById('recentProjects');if(!box||!state)return;box.innerHTML='';let items=(state.recent||[]).filter(Boolean).slice(0,8);if(!items.length){box.innerHTML='<div class="recentEmpty">No recent projects yet.<br>Open a folder to get started.</div>';return}items.forEach((p,i)=>{let d=document.createElement('div');d.className='recentProject';let parts=p.split(/[\\/]/),title=parts.pop()||p;d.innerHTML=`<div><b>${escapeHtml(title)}</b><small>${escapeHtml(p)}</small></div><span title="Remove from recent">×</span>`;d.onclick=async e=>{if(e.target.tagName==='SPAN'){e.stopPropagation();state.recent=state.recent.filter(x=>x!==p);persist();renderRecentProjects();return}let t=await api.loadFolder(p);if(t)loadFolder(t);else{state.recent=state.recent.filter(x=>x!==p);persist();renderRecentProjects()}};box.appendChild(d)})}
setTimeout(()=>{if(state){applyPeaBoxTheme(state.settings?.theme||'dark');renderRecentProjects()}},1600);
document.getElementById('themeToggle')?.addEventListener('click',()=>applyPeaBoxTheme(document.body.classList.contains('light')?'dark':'light'));
document.getElementById('welcomeOpen')?.addEventListener('click',()=>document.getElementById('open')?.click());
document.getElementById('welcomeNew')?.addEventListener('click',()=>document.getElementById('newFile')?.click());
document.getElementById('welcomePalette')?.addEventListener('click',()=>typeof openPalette==='function'&&openPalette());
document.getElementById('clearRecent')?.addEventListener('click',()=>{if(!state)return;state.recent=[];persist();renderRecentProjects()});
// v0.9 Developer Workflow
let editor2=null,diffEditor=null,splitCurrent=null,profile=null,currentView='editor';
function escapeV9(x){return String(x??'').replaceAll('&','&').replaceAll('<','<').replaceAll('>','>')}
async function initSecondEditor(){if(editor2)return;editor2=monaco.editor.create(document.getElementById('editor2'),{theme:document.body.classList.contains('light')?'vs':'vs-dark',automaticLayout:true,fontSize:14,minimap:{enabled:false}})}
async function splitEditor(){await initSecondEditor();let pane=document.getElementById('splitPane');pane.classList.toggle('hidden');if(!pane.classList.contains('hidden')&¤t){let r=await api.read(current);let u=monaco.Uri.parse('inmemory://split/'+encodeURIComponent(current));let m=monaco.editor.getModel(u)||monaco.editor.createModel(r.content,lang(current),u);editor2.setModel(m);splitCurrent=current;editor2.focus()}}
document.getElementById('splitBtn')?.addEventListener('click',splitEditor);
async function showMarkdown(){if(!current||!current.toLowerCase().endsWith('.md'))return setStatus('Open a Markdown file first');let host=document.getElementById('markdownPreview'),eds=document.getElementById('workspaceEditors'),diff=document.getElementById('diffHost');diff.classList.add('hidden');if(host.classList.contains('hidden')){let {marked}=window;host.innerHTML=marked.parse(editor.getValue());host.classList.remove('hidden');eds.classList.add('hidden');currentView='markdown'}else{host.classList.add('hidden');eds.classList.remove('hidden');currentView='editor'}}document.getElementById('previewBtn')?.addEventListener('click',showMarkdown);
async function showGitDiff(rel){if(!root)return;let abs=await api.absolute(root,rel),old=await api.gitContent(root,rel,'HEAD'),now='';try{now=(await api.read(abs)).content}catch{}let host=document.getElementById('diffHost');document.getElementById('workspaceEditors').classList.add('hidden');document.getElementById('markdownPreview').classList.add('hidden');host.classList.remove('hidden');if(diffEditor)diffEditor.dispose();diffEditor=monaco.editor.createDiffEditor(host,{theme:document.body.classList.contains('light')?'vs':'vs-dark',automaticLayout:true,readOnly:true,renderSideBySide:true});let original=monaco.editor.createModel(old.text||'',lang(abs));let modified=monaco.editor.createModel(now,lang(abs));diffEditor.setModel({original,modified});currentView='diff';setStatus('Diff: '+rel)}
async function refreshGitV9(){if(!root)return;let r=await api.git('status',root),box=document.getElementById('changes');if(!box)return;box.innerHTML='';let lines=(r.text||'').split(/\r?\n/).filter(Boolean);lines.slice(1).forEach(line=>{let code=line.slice(0,2),rel=line.slice(3).trim().replace(/^.* -> /,'');let d=document.createElement('div');d.className='gitFile';d.innerHTML=`<span class="gitCode">${escapeV9(code)}</span>${escapeV9(rel)}<span class="gitActions"><button title="Stage">+</button><button title="Diff">◧</button></span>`;d.onclick=e=>{if(!e.target.closest('button'))showGitDiff(rel)};let bs=d.querySelectorAll('button');bs[0].onclick=async e=>{e.stopPropagation();await api.git('stage',root,rel);refreshGitV9()};bs[1].onclick=e=>{e.stopPropagation();showGitDiff(rel)};box.appendChild(d)});let log=await api.gitLog(root);let lg=document.createElement('div');lg.innerHTML='<div class="head">RECENT COMMITS</div>'+escapeV9(log.text||'').split('\n').filter(Boolean).slice(0,8).map(x=>`<div class="gitLog">${x}</div>`).join('');box.appendChild(lg)}
async function loadProfile(){if(!root)return;profile=await api.profileLoad(root);renderDashboard()}
async function renderDashboard(){if(!root)return;let i=await api.projectInfo(root),box=document.getElementById('dashSide');if(!box)return;let git=(i.git||'').split(/\r?\n/),branch=(git[0]||'Not a Git repository').replace('## ','');let changed=Math.max(0,git.filter(Boolean).length-1);box.innerHTML=`<div class="dashCard"><h4>PROJECT</h4><div class="metric">${escapeV9(i.name)}</div><div class="dashRow"><span>Type</span><b>${escapeV9(i.type)}</b></div><div class="dashRow"><span>Files</span><b>${i.fileCount}</b></div></div><div class="dashCard"><h4>GIT</h4><div class="dashRow"><span>Branch</span><b>${escapeV9(branch)}</b></div><div class="dashRow"><span>Changes</span><b>${changed}</b></div></div><div class="dashCard"><h4>RUN PROFILES</h4><div id="profileActions"></div></div>`;let pbox=document.getElementById('profileActions');if(profile?.commands){Object.entries(profile.commands).forEach(([k,v])=>{let b=document.createElement('button');b.className='profileBtn';b.textContent='▶ '+k+' '+v;b.onclick=()=>runProfile(k);pbox.appendChild(b)})}else{let b=document.createElement('button');b.className='profileBtn';b.textContent='Create .peabox/project.json';b.onclick=createDefaultProfile;pbox.appendChild(b)}}
async function createDefaultProfile(){if(!root)return;let info=await api.projectInfo(root),commands=info.type==='Python'?{run:'python app.py',test:'pytest',install:'pip install -r requirements.txt'}:info.type.includes('Node')?{run:'npm.cmd start',test:'npm.cmd test',install:'npm.cmd install'}:{run:'echo Configure this command in .peabox/project.json'};await api.profileCreate(root,{name:info.name,commands});profile=await api.profileLoad(root);renderDashboard();refresh()}
function runProfile(k){if(!profile?.commands?.[k])return;let c=profile.commands[k];let ri=document.getElementById('runInput');if(ri)ri.value=c;typeof showOutput==='function'&&showOutput('> '+c+'\n');api.run(root,c)}
const _v9load=loadFolder;loadFolder=async function(t){await _v9load(t);await loadProfile();await refreshGitV9()};
const _v9open=openFile;openFile=async function(p,line){document.getElementById('diffHost')?.classList.add('hidden');document.getElementById('markdownPreview')?.classList.add('hidden');document.getElementById('workspaceEditors')?.classList.remove('hidden');currentView='editor';await _v9open(p,line)};
// Improve explorer icons and folder toggling after each render.
function enhanceExplorer(){document.querySelectorAll('#tree .node').forEach(n=>{if(n.dataset.v9)return;n.dataset.v9='1';let text=n.textContent.trim(),isFolder=text.startsWith('▾');n.innerHTML=`<span class="explorerIcon">${isFolder?'▾':'◇'}</span>${escapeV9(text.replace(/^▾\s*/,''))}`})}setInterval(enhanceExplorer,800);
// v0.10 Debug Adapter Protocol client UI
let dbg={active:false,threadId:null,frameId:null,breakpoints:new Map(),watches:[],scopes:[]};
function debugLog(x,cat='console'){let out=document.getElementById('debugConsoleOut');if(out){out.textContent+=String(x);out.scrollTop=out.scrollHeight}}
function debugStatus(x){let e=document.getElementById('debugStatus');if(e)e.textContent='Debug: '+x}
function debugProgram(){if(!current||lang(current)!=='python')return null;return current}
async function startDebug(){let program=debugProgram();if(!program)return setStatus('Open a Python file to debug');document.querySelector('[data-view="debug"]')?.click();document.getElementById('debugConsole')?.classList.remove('hidden');debugLog(`Starting ${program}\n`);debugStatus('starting');let r=await api.dapStart({cwd:root,program,python:'python',args:[]});if(!r.ok){debugLog('Debugger error: '+r.error+'\n','stderr');debugStatus('error');return}dbg.active=true;debugStatus('running');await syncAllBreakpoints();try{await api.dapReq('configurationDone',{})}catch{}}
async function toggleBreakpoint(){if(!current||!editor)return;let line=editor.getPosition().lineNumber,a=dbg.breakpoints.get(current)||new Set();a.has(line)?a.delete(line):a.add(line);dbg.breakpoints.set(current,a);renderBreakpoints();decorateBreakpoints();if(dbg.active)await syncBreakpoints(current)}
async function syncBreakpoints(file){let a=[...(dbg.breakpoints.get(file)||[])].sort((x,y)=>x-y);try{await api.dapReq('setBreakpoints',{source:{name:name(file),path:file},breakpoints:a.map(line=>({line})),sourceModified:false})}catch(e){debugLog('Breakpoint error: '+e.message+'\n')}}async function syncAllBreakpoints(){for(let p of dbg.breakpoints.keys())await syncBreakpoints(p)}
function renderBreakpoints(){let b=document.getElementById('breakpointList');if(!b)return;b.innerHTML='';for(let[p,set]of dbg.breakpoints)for(let line of [...set].sort((a,b)=>a-b)){let d=document.createElement('div');d.className='debugRow';d.innerHTML=`<span class="breakpointDot">●</span>${escapeV9(name(p))}:${line}`;d.onclick=()=>{openFile(p,line)};b.appendChild(d)}}
function decorateBreakpoints(){if(!editor||!current)return;let lines=[...(dbg.breakpoints.get(current)||[])];let old=editor._peaboxBreakDecor||[];editor._peaboxBreakDecor=editor.deltaDecorations(old,lines.map(line=>({range:new monaco.Range(line,1,line,1),options:{isWholeLine:true,className:'debugLineBreakpoint',glyphMarginClassName:'debugGlyph'}})))}
async function onStopped(body){dbg.threadId=body.threadId;debugStatus(body.reason||'paused');let st=await api.dapReq('stackTrace',{threadId:dbg.threadId,startFrame:0,levels:50}).catch(()=>({stackFrames:[]}));renderStack(st.stackFrames||[]);if(st.stackFrames?.length)await selectFrame(st.stackFrames[0])}
function renderStack(frames){let b=document.getElementById('callStack');b.innerHTML='';frames.forEach(f=>{let d=document.createElement('div');d.className='stackFrame';d.innerHTML=`${escapeV9(f.name)}<small>${escapeV9(f.source?.name||'')} : ${f.line}</small>`;d.onclick=()=>selectFrame(f);b.appendChild(d)})}
async function selectFrame(f){dbg.frameId=f.id;if(f.source?.path)await openFile(f.source.path,f.line);let q=await api.dapReq('scopes',{frameId:f.id}).catch(()=>({scopes:[]}));dbg.scopes=q.scopes||[];await renderVariables();await refreshWatches()}
async function renderVariables(){let b=document.getElementById('debugVars');b.innerHTML='';for(let sc of dbg.scopes){let h=document.createElement('div');h.className='debugRow';h.innerHTML=`<b>${escapeV9(sc.name)}</b>`;b.appendChild(h);let q=await api.dapReq('variables',{variablesReference:sc.variablesReference,start:0,count:100}).catch(()=>({variables:[]}));for(let v of q.variables||[]){let d=document.createElement('div');d.className='debugRow';d.style.paddingLeft='12px';d.innerHTML=`<span class="debugName">${escapeV9(v.name)}</span><span class="debugValue">${escapeV9(v.value)}</span><span class="debugType">${escapeV9(v.type||'')}</span>`;b.appendChild(d)}}}
async function refreshWatches(){let b=document.getElementById('watchList');b.innerHTML='';for(let expr of dbg.watches){let val='';try{let q=await api.dapReq('evaluate',{expression:expr,frameId:dbg.frameId,context:'watch'});val=q.result}catch(e){val=e.message}let d=document.createElement('div');d.className='debugRow';d.innerHTML=`<span class="debugName">${escapeV9(expr)}</span><span class="debugValue">${escapeV9(val)}</span>`;b.appendChild(d)}}
async function control(cmd){if(!dbg.active||!dbg.threadId)return;try{await api.dapReq(cmd,{threadId:dbg.threadId,singleThread:false});debugStatus('running')}catch(e){debugLog(e.message+'\n')}}
document.getElementById('debugStart')?.addEventListener('click',startDebug);document.getElementById('debugButton')?.addEventListener('click',startDebug);document.getElementById('dbgContinue')?.addEventListener('click',()=>control('continue'));document.getElementById('dbgOver')?.addEventListener('click',()=>control('next'));document.getElementById('dbgInto')?.addEventListener('click',()=>control('stepIn'));document.getElementById('dbgOut')?.addEventListener('click',()=>control('stepOut'));document.getElementById('dbgPause')?.addEventListener('click',()=>control('pause'));document.getElementById('dbgStop')?.addEventListener('click',async()=>{await api.dapStop();dbg.active=false;debugStatus('idle')});document.getElementById('addWatch')?.addEventListener('click',()=>{let x=prompt('Watch expression');if(x){dbg.watches.push(x);refreshWatches()}});document.getElementById('debugEval')?.addEventListener('keydown',async e=>{if(e.key==='Enter'&&dbg.frameId){let expr=e.target.value;e.target.value='';try{let q=await api.dapReq('evaluate',{expression:expr,frameId:dbg.frameId,context:'repl'});debugLog(`> ${expr}\n${q.result}\n`)}catch(x){debugLog(`> ${expr}\n${x.message}\n`)}}});
api.onDapStatus(debugStatus);api.onDapEvent(async e=>{if(e.event==='stopped')await onStopped(e.body||{});else if(e.event==='continued')debugStatus('running');else if(e.event==='output')debugLog(e.body?.output||'');else if(e.event==='terminated'||e.event==='exited'){dbg.active=false;debugStatus('stopped');debugLog(`\n[Debug session ended]\n`)}});
// Breakpoints: F9 or click the editor glyph margin.
setTimeout(()=>{if(editor){editor.updateOptions({glyphMargin:true});editor.onMouseDown(e=>{if(e.target.type===monaco.editor.MouseTargetType.GUTTER_GLYPH_MARGIN){editor.setPosition({lineNumber:e.target.position.lineNumber,column:1});toggleBreakpoint()}})}},1800);
window.addEventListener('keydown',e=>{if(e.key==='F9'){e.preventDefault();toggleBreakpoint()}if(e.key==='F5'){e.preventDefault();e.shiftKey?(api.dapStop(),debugStatus('idle')):(dbg.active?control('continue'):startDebug())}if(e.key==='F10'){e.preventDefault();control('next')}if(e.key==='F11'){e.preventDefault();e.shiftKey?control('stepOut'):control('stepIn')}});
// v0.11 reusable multi-language LSP Manager
const intelligence={servers:new Map(),versions:new Map(),changeTimers:new Map(),registered:new Set()};
function intelligentLanguage(p){let e=(p.split('.').pop()||'').toLowerCase();if(['py','pyi'].includes(e))return'python';if(e==='go')return'go';if(e==='rs')return'rust';if(['c','h','cc','cpp','cxx','hpp'].includes(e))return'cpp';return null}
function monacoIdsFor(l){return l==='python'?['python']:l==='go'?['go']:l==='rust'?['rust']:['c','cpp']}
function lspUri(p){return 'file:///'+p.replace(/\\/g,'/').replace(/^\//,'')}
function fromUri(u){return decodeURIComponent(u.replace(/^file:\/\/\//,'')).replace(/^\/([A-Za-z]:)/,'$1').replace(/\//g,navigator.platform.startsWith('Win')?'\\':'/')}
function lp(p){return{line:p.lineNumber-1,character:p.column-1}}function mr(r){return new monaco.Range(r.start.line+1,r.start.character+1,r.end.line+1,r.end.character+1)}
function mdoc(x){if(!x)return[];if(typeof x==='string')return[{value:x}];if(Array.isArray(x))return x.map(y=>({value:typeof y==='string'?y:y.value||''}));return[{value:x.value||''}]}
function genericWorkspaceEdit(w){let edits=[];if(w?.changes)for(let[u,es]of Object.entries(w.changes))for(let e of es)edits.push({resource:monaco.Uri.file(fromUri(u)),textEdit:{range:mr(e.range),text:e.newText},versionId:undefined});if(w?.documentChanges)for(let d of w.documentChanges||[])if(d.edits)for(let e of d.edits)edits.push({resource:monaco.Uri.file(fromUri(d.textDocument.uri)),textEdit:{range:mr(e.range),text:e.newText},versionId:undefined});return{edits}}
function registerGenericProviders(language){if(intelligence.registered.has(language))return;intelligence.registered.add(language);for(let id of monacoIdsFor(language)){
monaco.languages.registerCompletionItemProvider(id,{triggerCharacters:['.','(',':'],provideCompletionItems:async(m,p)=>{if(!current||intelligentLanguage(current)!==language)return{suggestions:[]};try{let r=await api.lspmReq(language,'textDocument/completion',{textDocument:{uri:lspUri(current)},position:lp(p),context:{triggerKind:1}}),a=Array.isArray(r)?r:r?.items||[];return{suggestions:a.map(x=>({label:typeof x.label==='string'?x.label:x.label?.label||'',kind:x.kind||1,detail:x.detail,documentation:mdoc(x.documentation)[0],insertText:x.insertText||x.textEdit?.newText||(typeof x.label==='string'?x.label:x.label?.label||''),insertTextRules:x.insertTextFormat===2?monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet:0,range:x.textEdit?.range?mr(x.textEdit.range):undefined}))}}catch{return{suggestions:[]}}}});
monaco.languages.registerHoverProvider(id,{provideHover:async(m,p)=>{try{let r=await api.lspmReq(language,'textDocument/hover',{textDocument:{uri:lspUri(current)},position:lp(p)});return r?{range:r.range?mr(r.range):undefined,contents:mdoc(r.contents)}:null}catch{return null}}});
monaco.languages.registerDefinitionProvider(id,{provideDefinition:async(m,p)=>{try{let r=await api.lspmReq(language,'textDocument/definition',{textDocument:{uri:lspUri(current)},position:lp(p)}),a=Array.isArray(r)?r:[r];return a.filter(Boolean).map(x=>({uri:monaco.Uri.file(fromUri(x.uri||x.targetUri)),range:mr(x.range||x.targetSelectionRange)}))}catch{return[]}}});
monaco.languages.registerReferenceProvider(id,{provideReferences:async(m,p,c)=>{try{let a=await api.lspmReq(language,'textDocument/references',{textDocument:{uri:lspUri(current)},position:lp(p),context:{includeDeclaration:c.includeDeclaration}});return(a||[]).map(x=>({uri:monaco.Uri.file(fromUri(x.uri)),range:mr(x.range)}))}catch{return[]}}});
monaco.languages.registerRenameProvider(id,{provideRenameEdits:async(m,p,n)=>{try{return genericWorkspaceEdit(await api.lspmReq(language,'textDocument/rename',{textDocument:{uri:lspUri(current)},position:lp(p),newName:n}))}catch(e){return{edits:[],rejectReason:e.message}}}});
monaco.languages.registerCodeActionProvider(id,{provideCodeActions:async(m,range,ctx)=>{try{let r=await api.lspmReq(language,'textDocument/codeAction',{textDocument:{uri:lspUri(current)},range:{start:lp(range.getStartPosition()),end:lp(range.getEndPosition())},context:{diagnostics:[]}});return{actions:(r||[]).filter(x=>x.edit).map(x=>({title:x.title,kind:x.kind,edit:genericWorkspaceEdit(x.edit)}))}}catch{return{actions:[]}}}});
}}
async function detectLanguageServers(){if(!root)return;let a=await api.lspmDetect(root),box=document.getElementById('languageServers');box.innerHTML='';let available=0;for(let x of a){let st=intelligence.servers.get(x.language)|| (x.available?'available':'missing');if(x.available)available++;let d=document.createElement('div');d.className='langServer';d.innerHTML=`<div class="langServerHead"><b>${escapeV9(x.language.toUpperCase())}</b><span class="langBadge ${st==='ready'?'ready':st==='missing'?'missing':''}">${escapeV9(st)}</span></div><small>${escapeV9(x.command)}</small>`;if(x.available){d.onclick=()=>ensureLanguageServer(x.language)}box.appendChild(d)}document.getElementById('multiLspStatus').textContent=`Languages: ${available} available`}
async function ensureLanguageServer(language){if(!root||intelligence.servers.get(language)==='ready')return true;intelligence.servers.set(language,'starting');let r=await api.lspmStart(language,root);if(!r.ok){intelligence.servers.set(language,'error');detectLanguageServers();setStatus(`${language} LSP: ${r.error}`);return false}intelligence.servers.set(language,'ready');registerGenericProviders(language);for(let[p,x]of models)if(intelligentLanguage(p)===language)openGenericDoc(p,x.model);detectLanguageServers();return true}
function openGenericDoc(p,m){let l=intelligentLanguage(p);if(!l||intelligence.servers.get(l)!=='ready')return;let k=l+'|'+p,v=intelligence.versions.get(k)||1;intelligence.versions.set(k,v);api.lspmOpen(l,p,m.getValue(),v)}
function syncGenericDoc(p,m){let l=intelligentLanguage(p);if(!l||intelligence.servers.get(l)!=='ready')return;let k=l+'|'+p,v=(intelligence.versions.get(k)||1)+1;intelligence.versions.set(k,v);clearTimeout(intelligence.changeTimers.get(k));intelligence.changeTimers.set(k,setTimeout(()=>api.lspmChange(l,p,m.getValue(),v),220))}
api.onLspmStatus(x=>{intelligence.servers.set(x.language,x.status);document.getElementById('multiLspStatus').textContent=`${x.language}: ${x.status}`;detectLanguageServers()});api.onLspmDiagnostics(x=>{let p=fromUri(x.params.uri),entry=models.get(p),markers=(x.params.diagnostics||[]).map(d=>({severity:d.severity===1?monaco.MarkerSeverity.Error:d.severity===2?monaco.MarkerSeverity.Warning:monaco.MarkerSeverity.Info,message:d.message,source:d.source||x.language,startLineNumber:d.range.start.line+1,startColumn:d.range.start.character+1,endLineNumber:d.range.end.line+1,endColumn:d.range.end.character+1}));if(entry)monaco.editor.setModelMarkers(entry.model,'lsp-'+x.language,markers);if(p===current)renderProblems(markers,p)});api.onLspmLog(x=>console.debug('[LSP '+x.language+']',x.text));
document.getElementById('detectServers')?.addEventListener('click',detectLanguageServers);
const _mlOpen=openFile;openFile=async function(p,line){let l=intelligentLanguage(p);if(l&&intelligence.servers.get(l)!=='ready')await ensureLanguageServer(l);await _mlOpen(p,line);if(l)openGenericDoc(p,models.get(p)?.model)};
const _mlLoad=loadFolder;loadFolder=async function(t){await _mlLoad(t);await detectLanguageServers()};
setTimeout(()=>{if(editor)editor.onDidChangeModelContent(()=>{if(current){let x=models.get(current);if(x)syncGenericDoc(current,x.model)}})},2000);