`).join('');const on=AC.el.children[AC.idx];if(on)on.scrollIntoView({block:'nearest'});}
function closeAC(){AC.on=false;if(AC.el)AC.el.remove();}
function acAccept(){const v=AC.view,p=v.primSel().h;const w=AC.items[AC.idx];closeAC();const s=v.doc.lines[p.l];let e=p.c;while(e{if(AC.on&&AC.view)acMaybe(AC.view,true);},0);return false;}
if(/^(Left|Right|Home|End)$/.test(k)||k.startsWith('Ctrl+')||k.startsWith('Alt+'))closeAC();return false;}
/* ================= flash / dialogs ================= */
let flashT=0;function flash(m,bad){const e=$('#sbMsg');e.textContent=m;e.style.color=bad?'#e53935':'';clearTimeout(flashT);flashT=setTimeout(()=>{e.textContent='';},bad?7000:4500);}
const DLGS=[];
function dlg(o){const ov=o.modal!==false?el('div','ovl'):null;const box=el('div','dlg');if(o.id)box.id=o.id;const hd=el('div','dh');hd.append(document.createTextNode(o.title||''));const x=el('span','dx','\u2715');hd.append(x);
const body=el('div','db');if(typeof o.body==='string')body.innerHTML=o.body;else if(o.body)body.append(o.body);box.append(hd,body);
let ft=null;if(o.buttons&&o.buttons.length){ft=el('div','df');for(const b of o.buttons){const bt=el('button',b.def?'def':'',escH(b.t));bt.type='button';bt.onclick=()=>{const r=b.fn?b.fn(api):undefined;if(r!==false&&!b.keep)api.close();};ft.append(bt);}box.append(ft);}
if(o.w)box.style.width=o.w+'px';
const api={el:box,body,close(){if(!box.isConnected)return;box.remove();if(ov)ov.remove();const i=DLGS.indexOf(api);if(i>=0)DLGS.splice(i,1);if(o.onClose)o.onClose();if(AV&&!DLGS.length)AV.focus();},ov};
if(ov){document.body.append(ov);ov.addEventListener('mousedown',e=>{if(e.target===ov&&o.modal==='soft')api.close();});}document.body.append(box);
const W=box.offsetWidth,H=box.offsetHeight;box.style.left=Math.max(4,(innerWidth-W)/2)+'px';box.style.top=Math.max(4,(innerHeight-H)/3)+'px';if(o.pos){box.style.left=o.pos.x+'px';box.style.top=o.pos.y+'px';}
x.onclick=()=>api.close();
hd.addEventListener('mousedown',e=>{if(e.target===x)return;e.preventDefault();const sx=e.clientX-box.offsetLeft,sy=e.clientY-box.offsetTop;const mv=ev=>{box.style.left=clamp(ev.clientX-sx,0,innerWidth-60)+'px';box.style.top=clamp(ev.clientY-sy,0,innerHeight-30)+'px';};const up=()=>{window.removeEventListener('mousemove',mv);window.removeEventListener('mouseup',up);if(o.onMove)o.onMove(box.offsetLeft,box.offsetTop);};window.addEventListener('mousemove',mv);window.addEventListener('mouseup',up);});
box.addEventListener('keydown',e=>{if(e.key==='Escape'){e.preventDefault();e.stopPropagation();api.close();}else if(e.key==='Enter'&&!e.shiftKey&&e.target.tagName!=='TEXTAREA'&&e.target.tagName!=='BUTTON'&&!o.noEnter){const b=ft&&ft.querySelector('button.def');if(b){e.preventDefault();b.click();}}});
DLGS.push(api);setTimeout(()=>{const f=box.querySelector('[autofocus]')||box.querySelector('input:not([type=checkbox]):not([type=radio]),select,textarea,button.def');if(f){f.focus();if(f.select&&f.type!=='number')try{f.select();}catch(e){}}},0);return api;}
function ask(title,msg,btns=['OK']){return new Promise(res=>{let done=false;const d=dlg({title,body:'
'+escH(msg)+'
',buttons:btns.map((t,i)=>({t,def:i===0,fn:()=>{done=true;res(t);}})),onClose:()=>{if(!done)res(btns.length>1?btns[btns.length-1]:btns[0]);}});});}
function prompt2(title,label,def='',o={}){return new Promise(res=>{let done=false;const b=el('div');b.innerHTML=``+(o.hint?`
${o.hint}
`:'');
const inp=b.querySelector('input');dlg({title,body:b,buttons:[{t:'OK',def:true,fn:()=>{done=true;res(inp.value);}},{t:'Cancel'}],onClose:()=>{if(!done)res(null);}});});}
/* ================= menus ================= */
let MENUS=[];
function closeMenus(){for(const m of MENUS)m.el.remove();MENUS=[];$$('.mb.open').forEach(x=>x.classList.remove('open'));}
function itemList(items){return(typeof items==='function'?items():items).filter(Boolean);}
function openMenu(items,x,y,level=0,anchor){while(MENUS.length>level)MENUS.pop().el.remove();const m=el('div','menu');const list=itemList(items);const rows=[];
for(const it of list){if(it==='-'){m.append(el('div','msep'));continue;}let o=it;if(typeof it==='string'){const c=CMD[it];if(!c)continue;o={t:c.name,id:it};}
const r=el('div','mi');const c=o.id?CMD[o.id]:null;const label=o.t||(c&&c.name)||'';const chk=o.chk?o.chk():c&&c.chk?c.chk():false;const dis=o.dis?o.dis():c&&c.dis?c.dis():false;
r.innerHTML=`${escH(label)}`+(o.sub?'':`${escH(o.key||(o.id?keyOf(o.id).split('|')[0]:''))}`);if(chk)r.classList.add('chk');if(dis)r.classList.add('dis');if(o.sub)r.classList.add('sub');
r._o=o;rows.push(r);m.append(r);
r.addEventListener('mouseenter',()=>{rows.forEach(z=>z.classList.remove('act'));r.classList.add('act');clearTimeout(m._t);m._t=setTimeout(()=>{if(o.sub&&!dis){const b=r.getBoundingClientRect();openMenu(o.sub,b.right-2,b.top-3,level+1,r);}else while(MENUS.length>level+1)MENUS.pop().el.remove();},o.sub?120:180);});
r.addEventListener('mousedown',e=>e.preventDefault());
r.addEventListener('click',e=>{e.stopPropagation();if(dis)return;if(o.sub){const b=r.getBoundingClientRect();openMenu(o.sub,b.right-2,b.top-3,level+1,r);return;}closeMenus();if(o.fn)o.fn();else if(o.id)run(o.id);});}
document.body.append(m);const W=m.offsetWidth,H=m.offsetHeight;if(x+W>innerWidth)x=level&&anchor?anchor.getBoundingClientRect().left-W+2:Math.max(0,innerWidth-W-2);if(y+H>innerHeight)y=Math.max(0,innerHeight-H-2);m.style.left=x+'px';m.style.top=y+'px';
const mo={el:m,rows,level};MENUS.push(mo);return mo;}
document.addEventListener('mousedown',e=>{if(MENUS.length&&!e.target.closest('.menu')&&!e.target.closest('.mb'))closeMenus();},true);
function menuKey(e){if(!MENUS.length)return false;const m=MENUS[MENUS.length-1];const rows=m.rows.filter(r=>!r.classList.contains('dis'));let i=rows.findIndex(r=>r.classList.contains('act'));
const k=e.key;if(k==='Escape'){e.preventDefault();if(MENUS.length>1)MENUS.pop().el.remove();else closeMenus();return true;}
if(k==='ArrowDown'||k==='ArrowUp'){e.preventDefault();i=(i+(k==='ArrowDown'?1:-1)+rows.length)%rows.length;m.rows.forEach(r=>r.classList.remove('act'));rows[i].classList.add('act');rows[i].scrollIntoView({block:'nearest'});return true;}
if(k==='ArrowRight'){e.preventDefault();const r=rows[i];if(r&&r._o.sub){r.click();const n=MENUS[MENUS.length-1];if(n.rows[0])n.rows[0].classList.add('act');}else menubarStep(1);return true;}
if(k==='ArrowLeft'){e.preventDefault();if(MENUS.length>1)MENUS.pop().el.remove();else menubarStep(-1);return true;}
if(k==='Enter'){e.preventDefault();const r=rows[i];if(r)r.click();return true;}
if(k.length===1){const r=rows.find(r=>r.textContent.trim().toLowerCase().startsWith(k.toLowerCase()));if(r){e.preventDefault();m.rows.forEach(z=>z.classList.remove('act'));r.classList.add('act');}return true;}
e.preventDefault();return true;}
let MBAR=[];
function openTop(i){const b=MBAR[i];if(!b)return;closeMenus();b.el.classList.add('open');const r=b.el.getBoundingClientRect();const m=openMenu(b.items,r.left,r.bottom);m.top=i;}
function menubarStep(d){const cur=MBAR.findIndex(b=>b.el.classList.contains('open'));if(cur<0)return;openTop((cur+d+MBAR.length)%MBAR.length);const m=MENUS[0];if(m&&m.rows[0])m.rows[0].classList.add('act');}
/* ================= keys ================= */
const CODEKEY={Minus:'-',Equal:'=',BracketLeft:'[',BracketRight:']',Backslash:'\\',Semicolon:';',Quote:"'",Comma:',',Period:'.',Slash:'/',Backquote:'`'};
const KEYALIAS={ArrowUp:'Up',ArrowDown:'Down',ArrowLeft:'Left',ArrowRight:'Right',' ':'Space',Esc:'Escape',Del:'Delete'};
function keyStr(e){let k=e.key;if(!k||k==='Unidentified'||['Control','Shift','Alt','Meta','AltGraph','CapsLock','OS'].includes(k))return null;const c=e.code||'';
if(/^Numpad/.test(c)&&!/^Numpad(Enter)$/.test(c)){const n=c.slice(6);k='Num'+(n==='Add'?'+':n==='Subtract'?'-':n==='Multiply'?'*':n==='Divide'?'/':n==='Decimal'?'.':n);if(/^Num\d$/.test(k)&&!e.getModifierState('NumLock')&&!e.ctrlKey&&!e.altKey)k=KEYALIAS[e.key]||e.key;}
else if(/^Digit\d$/.test(c))k=c.slice(5);else if(/^[a-z]$/i.test(k))k=k.toUpperCase();else if(/^Key[A-Z]$/.test(c)&&(e.ctrlKey||e.altKey||e.metaKey))k=c.slice(3);else if(CODEKEY[c])k=CODEKEY[c];else k=KEYALIAS[k]||k;
if(k.length===1&&!e.ctrlKey&&!e.altKey&&!e.metaKey&&!/^Num/.test(k))return null;
if(e.altKey&&e.ctrlKey&&e.key&&e.key.length===1&&!/^[a-z0-9]$/i.test(e.key)&&e.getModifierState&&e.getModifierState('AltGraph'))return null;
return(e.ctrlKey||e.metaKey?'Ctrl+':'')+(e.altKey?'Alt+':'')+(e.shiftKey?'Shift+':'')+k;}
const KEYMAP=new Map();
function keyOf(id){const c=CMD[id];if(!c)return'';return S.keys[id]!=null?S.keys[id]:c.key||'';}
function buildKeymap(){KEYMAP.clear();for(const id in CMD)for(const k of keyOf(id).split('|'))if(k&&!KEYMAP.has(k))KEYMAP.set(k,id);for(const m of S.macros)if(m.key&&!KEYMAP.has(m.key))KEYMAP.set(m.key,'macro:'+m.name);}
/* ================= commands ================= */
const CMD={};
function cmd(id,name,fn,key,o){CMD[id]=Object.assign({id,name,fn,key:key||''},o||{});}
const MAC={rec:false,steps:[],last:[],playing:false};
function run(id,arg){if(id.startsWith('macro:')){const m=S.macros.find(x=>x.name===id.slice(6));if(m)playMacro(m.steps,1);return;}const c=CMD[id];if(!c)return;
if(c.needDoc!==false&&!AV)return;if(MAC.rec&&!c.norec)MAC.steps.push({t:'c',id});try{const r=c.fn(arg);if(r&&r.catch)r.catch(e=>{console.error(e);flash(e.message||String(e),true);});}catch(e){console.error(e);flash('Error: '+(e.message||e),true);}queueUI();}
function av(){return AV;}function ad(){return AV&&AV.doc;}
/* ================= panes / docs ================= */
const panes=[];let AV=null;const CLOSED=[];
class Pane{constructor(i){this.i=i;this.el=$('#pane'+i);this.tabbar=el('div','tabbar');this.docs=[];this.view=new View(this);this.el.append(this.tabbar,this.view.ed);
const tb=this.tabbar;
tb.addEventListener('mousedown',e=>{const t=e.target.closest('.tab');if(!t)return;const d=this.docs[+t.dataset.i];if(!d)return;if(e.target.classList.contains('x')){e.preventDefault();return;}
if(e.button===1){e.preventDefault();closeDoc(d,this);return;}if(e.button===0){showDoc(d,this);}});
tb.addEventListener('click',e=>{if(e.target.classList.contains('x')){const t=e.target.closest('.tab');closeDoc(this.docs[+t.dataset.i],this);}});
tb.addEventListener('dblclick',e=>{const t=e.target.closest('.tab');if(!t){run('new');return;}if(S.dblClickClose)closeDoc(this.docs[+t.dataset.i],this);});
tb.addEventListener('contextmenu',e=>{e.preventDefault();const t=e.target.closest('.tab');if(!t)return;const d=this.docs[+t.dataset.i];showDoc(d,this);openMenu(tabCtxMenu(),e.clientX,e.clientY);});
tb.addEventListener('dragstart',e=>{const t=e.target.closest('.tab');if(!t)return;TABDRAG={pane:this,i:+t.dataset.i};e.dataTransfer.effectAllowed='move';e.dataTransfer.setData('application/x-npp-tab','1');});
tb.addEventListener('dragover',e=>{if(!TABDRAG)return;e.preventDefault();$$('.tab.drop').forEach(x=>x.classList.remove('drop'));const t=e.target.closest('.tab');if(t)t.classList.add('drop');});
tb.addEventListener('drop',e=>{if(!TABDRAG)return;e.preventDefault();e.stopPropagation();$$('.tab.drop').forEach(x=>x.classList.remove('drop'));const t=e.target.closest('.tab');const to=t?+t.dataset.i:this.docs.length;
const src=TABDRAG.pane,d=src.docs[TABDRAG.i];TABDRAG=null;if(!d)return;if(src===this){const arr=this.docs;arr.splice(arr.indexOf(d),1);arr.splice(Math.min(to,arr.length),0,d);renderTabs(this);}else moveDoc(d,src,this,to);});
tb.addEventListener('dragend',()=>{TABDRAG=null;$$('.tab.drop').forEach(x=>x.classList.remove('drop'));});
tb.addEventListener('wheel',e=>{if(e.deltaY)tb.scrollLeft+=e.deltaY;},{passive:true});
this.el.addEventListener('mousedown',()=>{if(AV!==this.view)setActive(this.view);});}
get cur(){return this.view.doc;}
visible(){return!this.el.classList.contains('hide');}}
let TABDRAG=null;
const ICON_SAVED='';
const ICON_DIRTY='';
const ICON_RO='';
const ICON_MON='';
function renderTabs(p){const tb=p.tabbar;tb.classList.toggle('ml',!!S.multiLineTabs);let h='';p.docs.forEach((d,i)=>{const act=p.view.doc===d;
h+=`
`;});
tb.innerHTML=h;const a=tb.querySelector('.tab.act');if(a){const r=a.getBoundingClientRect(),R=tb.getBoundingClientRect();if(r.right>R.right||r.leftp.view.doc===d)||panes.find(p=>p.docs.includes(d));}
function newDoc(pane){const d=new Doc({isNew:true,lang:S.defLang});d.setText('');return showDoc(d,pane);}
function docInUse(d){return panes.some(p=>p.docs.includes(d));}
async function closeDoc(d,pane,o={}){pane=pane||paneOf(d);if(!pane)return true;const other=panes.find(p=>p!==pane&&p.docs.includes(d));
if(!other&&d.dirty&&!o.force){showDoc(d,pane);const r=await ask('Save',`Save file "${d.name}" ?`,['Yes','No','Cancel']);if(r==='Cancel')return false;if(r==='Yes'&&!await saveDoc(d))return false;}
if(d.monitor)toggleMonitor(d,false);const i=pane.docs.indexOf(d);pane.docs.splice(i,1);pane.view.state.delete(d.id);
if(!other){if(d.lines.length>1||d.lines[0]||d.handle)CLOSED.push({name:d.name,text:d.text(),handle:d.handle,lang:d.lang,enc:d.enc,bom:d.bom,eol:d.eol});if(CLOSED.length>20)CLOSED.shift();ALLDOCS.delete(d);if(CMP.on&&(CMP.a===d||CMP.b===d))clearCompare();}
if(pane.view.doc===d){if(pane.docs.length){showDoc(pane.docs[Math.min(i,pane.docs.length-1)],pane,!o.noFocus);}else{pane.view.doc=null;
if(pane.i===1){showPane(pane,false);if(panes[0].view.doc)setActive(panes[0].view),panes[0].view.focus();}
else if(panes[1].docs.length){const p1=panes[1];const docs=p1.docs.slice(),cur=p1.view.doc;for(const x of docs){pane.docs.push(x);}p1.docs=[];p1.view.doc=null;showPane(p1,false);showDoc(cur,pane);}
else newDoc(pane);}}
renderTabs(pane);SESS.dirty=true;queueUI();return true;}
function moveDoc(d,src,dst,at){const clone=dst.docs.includes(d);src.docs.splice(src.docs.indexOf(d),1);if(!clone)dst.docs.splice(at==null?dst.docs.length:Math.min(at,dst.docs.length),0,d);
if(src.view.doc===d){if(src.docs.length)src.view.setDoc(src.docs[Math.min(src.docs.length-1,0)]);else{src.view.doc=null;if(src.i===1)showPane(src,false);else{src.docs=dst.docs;dst.docs=[];const c=d;dst.view.doc=null;showPane(dst,false);src.view.doc=null;showDoc(c,src);renderTabs(src);return;}}}
renderTabs(src);showDoc(d,dst);}
function cloneDoc(d,src,dst){if(dst.docs.includes(d)){showDoc(d,dst);return;}dst.docs.push(d);showDoc(d,dst);}
function allDocs(){const s=[];for(const p of panes)for(const d of p.docs)if(!s.includes(d))s.push(d);return s;}
/* ================= file I/O ================= */
const hasFSA=!!window.showOpenFilePicker;
async function ensurePerm(h,mode){if(!h||!h.queryPermission)return true;try{let p=await h.queryPermission({mode});if(p==='granted')return true;p=await h.requestPermission({mode});return p==='granted';}catch(e){return false;}}
async function findOpenByHandle(h){for(const d of allDocs())if(d.handle&&d.handle.isSameEntry)try{if(await d.handle.isSameEntry(h))return d;}catch(e){}return null;}
async function openHandle(h,o={}){const ex=await findOpenByHandle(h);if(ex){showDoc(ex,paneOf(ex));return ex;}if(!await ensurePerm(h,'read')){flash('Permission denied for '+h.name,true);return null;}const f=await h.getFile();const d=await openFileObj(f,{handle:h});addRecent(h);return d;}
async function openFileObj(f,o={}){if(f.size>300e6&&await ask('Large file',`${f.name} is ${(f.size/1048576).toFixed(0)} MB. Opening it may be slow. Continue?`,['Open','Cancel'])!=='Open')return null;
flash('Loading '+f.name+'…');const buf=new Uint8Array(await f.arrayBuffer());const d=docFromBytes(buf,{name:f.name,handle:o.handle||null,mtime:f.lastModified,enc:o.enc});d.isNew=false;
const cur=AV&&AV.doc;const pane=AV?AV.pane:panes[0];showDoc(d,pane);if(cur&&cur.isNew&&!cur.dirty&&cur.lines.length===1&&!cur.lines[0]&&pane.docs.length>1){closeDoc(cur,pane,{force:true,noFocus:true});}
if(d.lang==='text'&&d.raw&&d.raw.length>0){let z=0;const n=Math.min(d.raw.length,8000);for(let i=0;in*.05&&!d.enc.startsWith('utf-16'))flash('This looks like a binary file; editing and saving may corrupt it.',true);}
flash('Opened '+f.name+' ('+fmtN(f.size)+' bytes, '+encLabel(d)+', '+EOLSHORT[d.eol]+')');return d;}
async function openFiles(){if(hasFSA){try{const hs=await showOpenFilePicker({multiple:true});for(const h of hs)await openHandle(h);}catch(e){if(e.name!=='AbortError')flash(e.message,true);}}else $('#filePick').click();}
$('#filePick').addEventListener('change',async e=>{for(const f of e.target.files)await openFileObj(f);e.target.value='';});
function markSaved(d,bytes){d.savedCid=d.cid;for(const[k,v]of d.chg)if(v===1)d.chg.set(k,2);d.isNew=false;if(bytes&&bytes.length<20e6)d.raw=bytes;for(const p of panes)renderTabs(p);refreshDoc(d);SESS.dirty=true;queueUI();}
async function writeHandle(h,bytes){const w=await h.createWritable();await w.write(bytes);await w.close();}
async function saveDoc(d,mode){d=d||ad();if(!d)return false;const{bytes,bad}=docBytes(d);if(bad)flash(`${bad} character(s) cannot be represented in ${encLabel(d)} and were saved as "?"`,true);
if(mode!=='as'&&mode!=='copy'&&d.handle){try{if(!await ensurePerm(d.handle,'readwrite'))throw new Error('Write permission was not granted');await writeHandle(d.handle,bytes);markSaved(d,bytes);try{d.mtime=(await d.handle.getFile()).lastModified;}catch(e){}flash('Saved '+d.name);return true;}
catch(e){flash('Could not write '+d.name+': '+e.message+'. Choose a location instead.',true);}}
if(hasFSA&&window.showSaveFilePicker){try{const h=await showSaveFilePicker({suggestedName:d.name});await writeHandle(h,bytes);
if(mode!=='copy'){d.handle=h;const old=d.name;d.name=h.name;if(d.lang==='text'||old.startsWith('new '))d.lang=detectLang(h.name,d.lines[0]);d.tokSt=[null];d.tokOK=0;d.foldVer=-1;scheduleFolds(d);markSaved(d,bytes);addRecent(h);try{d.mtime=(await h.getFile()).lastModified;}catch(e){}}
flash((mode==='copy'?'Saved a copy as ':'Saved ')+h.name);return true;}catch(e){if(e.name==='AbortError')return false;flash(e.message,true);}}
let name=d.name;if(mode==='as'||mode==='copy'||d.isNew||!d.handle){name=await prompt2(mode==='copy'?'Save a Copy As':'Save As','File name (downloaded by the browser):',d.name.startsWith('new ')?d.name+'.txt':d.name);if(!name)return false;}
download(name,new Blob([bytes]));if(mode!=='copy'){if(d.name!==name){d.name=name;if(d.lang==='text')d.lang=detectLang(name,d.lines[0]);d.tokSt=[null];d.tokOK=0;}markSaved(d,bytes);}flash('Downloaded '+name);return true;}
async function saveAll(){for(const d of allDocs())if(d.dirty&&!await saveDoc(d))return false;return true;}
async function reloadDoc(d,quiet){d=d||ad();if(!d.handle){flash('This document is not linked to a disk file',true);return;}if(!quiet&&d.dirty&&await ask('Reload',`Reload "${d.name}"? Your unsaved changes will be lost.`,['Reload','Cancel'])!=='Reload')return;
if(!await ensurePerm(d.handle,'read'))return;const f=await d.handle.getFile();const buf=new Uint8Array(await f.arrayBuffer());const{text,enc,bom}=decodeBytes(buf,d.enc);
const views2=views.filter(v=>v.doc===d);const keep=views2.map(v=>({v,p:v.primSel().h,st:v.sc.scrollTop}));const t=text.replace(/\r\n?/g,'\n');
if(d.monitor){openGroup(d,'reload',null);rawReplace(d,P(0,0),docEnd(d),t);closeGroup(d);d.undo.length=0;}else{d.setText(t);}d.enc=enc;d.bom=bom;d.eol=detectEOL(text)||d.eol;d.mtime=f.lastModified;d.savedCid=d.cid;d.raw=buf.length<20e6?buf:null;d.chg=new Map();
for(const{v,p,st}of keep){v.layoutDirty=true;v.tokCache.clear();if(d.monitor){v.setSels([SEL(docEnd(d))]);}else{v.sels=[SEL(clampPos(d,p))];v.sc.scrollTop=st;}v.schedule();}for(const p of panes)renderTabs(p);queueUI();if(!quiet)flash('Reloaded '+d.name);}
async function checkExternal(){for(const d of allDocs()){if(!d.handle||d.monitor||d._checking)continue;try{if(d.handle.queryPermission&&await d.handle.queryPermission({mode:'read'})!=='granted')continue;d._checking=true;const f=await d.handle.getFile();
if(d.mtime&&f.lastModified!==d.mtime){d.mtime=f.lastModified;const r=await ask('Reload',`"${d.name}"\n\nThis file has been modified by another program.\nDo you want to reload it${d.dirty?' and lose the changes made in Notepad':''}?`,['Yes','No']);if(r==='Yes')await reloadDoc(d,true);}}catch(e){if(e.name==='NotFoundError'&&!d._gone){d._gone=true;flash(`"${d.name}" no longer exists on disk`,true);}}finally{d._checking=false;}}}
window.addEventListener('focus',()=>setTimeout(checkExternal,150));
function toggleMonitor(d,on){d=d||ad();if(!d)return;on=on==null?!d.monitor:on;if(on&&!d.handle){flash('Monitoring needs a file opened from disk',true);return;}d.monitor=on;d.ro=on||d._ro;
if(d._mon){clearInterval(d._mon);d._mon=0;}if(on){d._mon=setInterval(async()=>{try{const f=await d.handle.getFile();if(f.lastModified!==d.mtime)await reloadDoc(d,true);}catch(e){}},1000);}for(const p of panes)renderTabs(p);queueUI();}
/* recent */
if(!Array.isArray(S.recent))S.recent=[];
async function addRecent(h){try{const key='h:'+h.name+':'+Date.now();await IDB.set(key,h);S.recent=S.recent.filter(r=>r.name!==h.name);S.recent.unshift({name:h.name,key});S.recent.length=Math.min(S.recent.length,S.recentMax);saveSettings();}catch(e){}}
async function openRecent(r){try{const h=await IDB.get(r.key);if(!h){flash('Recent entry is no longer available',true);return;}await openHandle(h);}catch(e){flash(e.message,true);}}
/* drag & drop files */
window.addEventListener('dragover',e=>{if([...(e.dataTransfer.types||[])].includes('Files')){e.preventDefault();e.dataTransfer.dropEffect='copy';}});
window.addEventListener('drop',async e=>{if(!e.dataTransfer.files||!e.dataTransfer.files.length)return;e.preventDefault();const items=[...e.dataTransfer.items||[]];
if(items.length&&items[0].getAsFileSystemHandle){const hs=await Promise.all(items.filter(i=>i.kind==='file').map(i=>i.getAsFileSystemHandle()));for(const h of hs){if(!h)continue;if(h.kind==='directory'){wsAddRoot(h);continue;}await openHandle(h);}}
else for(const f of e.dataTransfer.files)await openFileObj(f);});
/* ================= session ================= */
const SESS={dirty:false,saving:false};
function docState(d){const p=paneOf(d);const v=p&&p.view;let sels,st=0;if(v&&v.doc===d){sels=v.sels;st=v.sc.scrollTop;}else if(p&&p.view.state.has(d.id)){const s=p.view.state.get(d.id);sels=s.sels;st=s.st;}return{sels:(sels||[SEL(P(0,0))]).map(s=>({a:s.a,h:s.h})),st};}
async function saveSession(){if(SESS.saving)return;SESS.saving=true;try{const docs=allDocs().map(d=>{const big=d.lines.length>2e6;const st=docState(d);
return{id:d.id,name:d.name,text:big&&!d.dirty&&d.handle?null:d.text(),lang:d.lang,enc:d.enc,bom:d.bom,eol:d.eol,dirty:d.dirty,isNew:d.isNew,handle:d.handle,bm:[...d.bm],folded:[...d.folded],ro:!!d._ro,tabColor:d.tabColor,mtime:d.mtime,sels:st.sels,st:st.st};});
const data={v:1,docs,panes:panes.map(p=>({docs:p.docs.map(d=>d.id),cur:p.view.doc&&p.view.doc.id,vis:p.visible()})),act:AV?AV.pane.i:0,newSeq:NEWSEQ};
try{await IDB.set('session',data);}catch(e){data.docs.forEach(x=>x.handle=null);await IDB.set('session',data);}SESS.dirty=false;}catch(e){console.warn('session',e);}finally{SESS.saving=false;}}
async function restoreSession(data){if(!data||!data.docs||!data.docs.length)return false;const map=new Map();
for(const x of data.docs){const d=new Doc({name:x.name,handle:x.handle||null,enc:x.enc,bom:x.bom,eol:x.eol,lang:x.lang,mtime:x.mtime});
if(x.text==null&&x.handle){try{if(x.handle.queryPermission&&await x.handle.queryPermission({mode:'read'})==='granted'){const f=await x.handle.getFile();d.setText(decodeBytes(new Uint8Array(await f.arrayBuffer()),x.enc).text.replace(/\r\n?/g,'\n'));}else continue;}catch(e){continue;}}else d.setText(x.text||'');
d.isNew=x.isNew;if(x.dirty)d.savedCid=-1;d.bm=new Set(x.bm||[]);d._fold=x.folded||[];d._ro=!!x.ro;d.ro=d._ro;d.tabColor=x.tabColor||0;d._st=x;if(!LANGS[d.lang])d.lang='text';map.set(x.id,d);}
if(!map.size)return false;NEWSEQ=Math.max(NEWSEQ,data.newSeq||0);
data.panes.forEach((pp,i)=>{const p=panes[i];for(const id of pp.docs){const d=map.get(id);if(d&&!p.docs.includes(d))p.docs.push(d);}
for(const d of p.docs){const x=d._st;p.view.state.set(d.id,{sels:(x.sels||[]).map(s=>({a:clampPos(d,s.a),h:clampPos(d,s.h)})),prim:0,st:x.st||0,sl:0});}});
if(!panes[0].docs.length&&panes[1].docs.length){panes[0].docs=panes[1].docs;panes[1].docs=[];}if(!panes[0].docs.length)return false;
for(let i=1;i>=0;i--){const p=panes[i];if(!p.docs.length)continue;const cur=map.get(data.panes[i].cur);showDoc(cur&&p.docs.includes(cur)?cur:p.docs[0],p,false);}
for(const d of map.values()){if(d._fold&&d._fold.length){computeFolds(d);for(const f of d._fold)if(d.foldMap.has(f))d.folded.add(f);refreshDoc(d);}}
const act=panes[data.act]&&panes[data.act].docs.length?panes[data.act]:panes[0];setActive(act.view);act.view.focus();for(const p of panes)renderTabs(p);return true;}
document.addEventListener('visibilitychange',()=>{if(document.hidden&&S.session&&SESS.dirty)saveSession();});
window.addEventListener('pagehide',()=>{if(S.session&&SESS.dirty)saveSession();});
window.addEventListener('beforeunload',e=>{if(S.session&&S.snapshot){if(SESS.dirty)saveSession();return;}if(allDocs().some(d=>d.dirty)){e.preventDefault();e.returnValue='';}});
/* ================= edit helpers ================= */
const unitStr=()=>S.useSpaces?' '.repeat(S.tabSize):'\t';
function nonEmptySels(v){return v.sels.filter(s=>!sEmpty(s));}
function transformSel(fn,o={}){const v=AV,d=v.doc;let ss=nonEmptySels(v);let ranges;
if(ss.length)ranges=ss.map(s=>({from:sFrom(s),to:sTo(s),fw:cmp(s.h,s.a)>=0}));
else if(o.word){ranges=v.sels.map(s=>{const[a,b]=wordAtWord(d.lines[s.h.l],s.h.c);return{from:P(s.h.l,a),to:P(s.h.l,b),fw:true,keep:s.h};}).filter(r=>r.from.cSEL(clampPos(d,r.keep))));}else if(ranges[0].whole){const p=v.primSel().h;v.apply(ch,o.type||'xform',{noSel:true});v.setSels([SEL(clampPos(d,p))]);}else v.apply(ch,o.type||'xform');}
function lineRange(v,forceDoc){const d=v.doc;const ps=v.primSel();if(!forceDoc&&v.sels.some(s=>s.a.l!==s.h.l)){const ls=v.selLines();return[ls[0],ls[ls.length-1],true];}if(!forceDoc&&v.rect)return[Math.min(v.rect.al,v.rect.hl),Math.max(v.rect.al,v.rect.hl),true];return[0,d.lines.length-1,false];}
function lineOps(fn,o={}){const v=AV,d=v.doc;if(d.ro){flash('Document is read-only',true);return;}const[a,b,hadSel]=o.cur?[v.primSel().h.l,v.primSel().h.l,false]:lineRange(v,o.doc);const old=d.lines.slice(a,b+1);let nw=fn(old,a,b);if(!nw)return;
if(nw.length===old.length&&nw.every((x,i)=>x===old[i])){flash(o.none||'Nothing to change');return;}const p=v.primSel().h;
if(!nw.length){if(b+10)v.apply([{from:P(a-1,d.lines[a-1].length),to:P(b,d.lines[b].length),text:''}],o.type||'lines',{noSel:true});else v.apply([{from:P(0,0),to:docEnd(d),text:''}],o.type||'lines',{noSel:true});v.setSels([SEL(clampPos(d,P(a,0)))]);flash(o.msg?o.msg(old.length,0):'');return;}
v.apply([{from:P(a,0),to:P(b,d.lines[b].length),text:nw.join('\n')}],o.type||'lines',{noSel:true});const e=a+nw.length-1;
if(hadSel)v.setSels([{a:P(a,0),h:P(e,d.lines[e].length)}]);else v.setSels([SEL(clampPos(d,p))]);if(o.msg)flash(o.msg(old.length,nw.length));}
function sortKeyFn(v,a){if(v.rect&&v.sels.length>1){const R=v.rect,v0=Math.min(R.av,R.hv),v1=Math.max(R.av,R.hv);return s=>{const i=idxAtVcol(s,v0),j=idxAtVcol(s,v1);return s.slice(i,j);};}return s=>s;}
function sortLines(cmpf,keyMode){const v=AV;const key=sortKeyFn(v);lineOps((ls)=>{const arr=ls.map((s,i)=>({s,k:key(s),i}));
if(keyMode){const parse=keyMode==='int'?x=>{const m=/-?\d+/.exec(x);return m?parseInt(m[0],10):NaN;}:keyMode==='comma'?x=>{const m=/-?\d+(?:\.\d{3})*(?:,\d+)?/.exec(x);return m?parseFloat(m[0].replace(/\./g,'').replace(',','.')):NaN;}:x=>{const m=/-?\d*\.?\d+(?:[eE][+-]?\d+)?/.exec(x);return m?parseFloat(m[0]):NaN;};
for(const o of arr)o.n=parse(o.k);const num=arr.filter(o=>!isNaN(o.n)),bad=arr.filter(o=>isNaN(o.n));num.sort((x,y)=>cmpf(x.n,y.n)||x.i-y.i);return bad.map(o=>o.s).concat(num.map(o=>o.s));}
arr.sort((x,y)=>cmpf(x.k,y.k)||x.i-y.i);return arr.map(o=>o.s);},{type:'sort',none:'Lines are already sorted'});}
const cA=(a,b)=>ab?1:0,cD=(a,b)=>-cA(a,b),cAi=(a,b)=>cA(a.toLowerCase(),b.toLowerCase()),cDi=(a,b)=>-cAi(a,b);
const collator=new Intl.Collator(undefined,{numeric:false,sensitivity:'variant'});
function properCase(t,blend){return t.replace(/[\p{L}\p{N}_']+/gu,w=>w[0].toUpperCase()+(blend?w.slice(1):w.slice(1).toLowerCase()));}
function sentenceCase(t,blend){let start=true,o='';for(const ch of t){if(/\p{L}/u.test(ch)){o+=start?ch.toUpperCase():blend?ch:ch.toLowerCase();start=false;}else{o+=ch;if(/[.!?]/.test(ch))start=true;}}return o;}
function invertCase(t){let o='';for(const ch of t){const u=ch.toUpperCase();o+=ch===u?ch.toLowerCase():u;}return o;}
function randomCase(t){let o='';for(const ch of t)o+=Math.random()<.5?ch.toUpperCase():ch.toLowerCase();return o;}
function lineComment(mode){const v=AV,d=v.doc,L=langOf(d);const cm=L.cmt&&L.cmt[0];const bo=L.cmt&&L.cmt[1],bc=L.cmt&&L.cmt[2];
if(!cm&&!bo){flash('This language has no comment syntax');return;}const ls=v.selLines();const ch=[];
if(!cm){for(const l of ls){const s=d.lines[l];const t=s.trim();if(!t)continue;const ws=leadWS(s).length;const isC=t.startsWith(bo)&&t.endsWith(bc);
if(mode==='un'||(mode==='toggle'&&isC)){if(isC){const a=s.indexOf(bo),b=s.lastIndexOf(bc);ch.push({from:P(l,b),to:P(l,b+bc.length),text:''},{from:P(l,a),to:P(l,a+bo.length),text:''});}}
else if(mode!=='un')ch.push({from:P(l,ws),to:P(l,ws),text:bo},{from:P(l,s.length),to:P(l,s.length),text:bc});}}
else{const ne=ls.filter(l=>d.lines[l].trim());const all=ne.length&&ne.every(l=>d.lines[l].trimStart().startsWith(cm));const minInd=Math.min(...ne.map(l=>leadWS(d.lines[l]).length));
for(const l of ls){const s=d.lines[l];const ws=leadWS(s).length;if(!s.trim()&&ls.length>1)continue;const isC=s.trimStart().startsWith(cm);
if(mode==='un'||(mode==='toggle'&&all)){if(isC){let n=cm.length;if(s[ws+n]===' '&&mode!=='raw')n++;ch.push({from:P(l,ws),to:P(l,ws+n),text:''});}}
else{const at=ls.length>1&&isFinite(minInd)?Math.min(minInd,ws):ws;ch.push({from:P(l,at),to:P(l,at),text:cm+' '});}}}
if(!ch.length)return;const old=v.sels.map(s=>({a:s.a,h:s.h}));edit(d,ch,'comment',v);v.setSels(v.sels,{prim:v.prim});}
function blockComment(un){const v=AV,d=v.doc,L=langOf(d);const bo=L.cmt&&L.cmt[1],bc=L.cmt&&L.cmt[2];if(!bo){if(!un)lineComment('add');else lineComment('un');return;}
const ss=v.sels.map(s=>sEmpty(s)?{a:P(s.h.l,0),h:P(s.h.l,d.lines[s.h.l].length)}:s);
if(un){const ch=[];for(const s of ss){const t=getRange(d,sFrom(s),sTo(s));const a=t.indexOf(bo),b=t.lastIndexOf(bc);if(a<0||b<=a)continue;const ta=t.slice(0,a)+t.slice(a+bo.length,b)+t.slice(b+bc.length);ch.push({from:sFrom(s),to:sTo(s),text:ta,selA:0,selH:ta.length});}if(ch.length)v.apply(ch,'comment');else flash('No block comment found in the selection');return;}
v.apply(ss.map(s=>{const t=getRange(d,sFrom(s),sTo(s));const n=bo+t+bc;return{from:sFrom(s),to:sTo(s),text:n,selA:0,selH:n.length};}),'comment');}
function fmtDate(f,dt=new Date()){const pad=(n,w=2)=>String(n).padStart(w,'0');const DN=['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'],MN=['January','February','March','April','May','June','July','August','September','October','November','December'];
return f.replace(/yyyy|yy|MMMM|MMM|MM|M|dddd|ddd|dd|d|HH|H|hh|h|mm|m|ss|s|tt|fff/g,t=>{switch(t){case'yyyy':return dt.getFullYear();case'yy':return pad(dt.getFullYear()%100);case'MMMM':return MN[dt.getMonth()];case'MMM':return MN[dt.getMonth()].slice(0,3);case'MM':return pad(dt.getMonth()+1);case'M':return dt.getMonth()+1;
case'dddd':return DN[dt.getDay()];case'ddd':return DN[dt.getDay()].slice(0,3);case'dd':return pad(dt.getDate());case'd':return dt.getDate();case'HH':return pad(dt.getHours());case'H':return dt.getHours();case'hh':return pad(dt.getHours()%12||12);case'h':return dt.getHours()%12||12;
case'mm':return pad(dt.getMinutes());case'm':return dt.getMinutes();case'ss':return pad(dt.getSeconds());case's':return dt.getSeconds();case'tt':return dt.getHours()<12?'AM':'PM';case'fff':return pad(dt.getMilliseconds(),3);}return t;});}
function insertAtSels(t){AV.insertText(t,'insert');}
async function clipWrite(t){CLIP.text=t;CLIP.rect=false;CLIP.line=false;clipHist(t);try{await navigator.clipboard.writeText(t);return true;}catch(e){const ta=el('textarea');ta.value=t;ta.style.cssText='position:fixed;left:-9999px';document.body.append(ta);ta.select();let ok=false;try{ok=document.execCommand('copy');}catch(x){}ta.remove();if(AV)AV.focus();if(!ok)flash('Clipboard access was blocked by the browser',true);return ok;}}
async function clipRead(){try{return await navigator.clipboard.readText();}catch(e){flash('The browser blocked clipboard reading; press Ctrl+V instead',true);return null;}}
function doCopy(cut){const v=AV;const r=v.copyText();if(!r)return;clipWrite(r.text.replace(/\n/g,v.doc.eol)).then(()=>{CLIP.text=r.text;CLIP.line=r.line;CLIP.rect=!!v.rect&&v.sels.length>1;});if(cut)v.cutSel(r);}
const CLIPH=[];function clipHist(t){if(!t)return;const i=CLIPH.indexOf(t);if(i>=0)CLIPH.splice(i,1);CLIPH.unshift(t);if(CLIPH.length>40)CLIPH.pop();panelRefresh('clip');}
function spacesToTabs(s,leadOnly){const tab=S.tabSize;let out='',v=0,i=0,lead=true;while(i1){out+='\t';cur=nt;}else if(nt<=nv&&nt-cur===1){out+=(j-i===1)?' ':'\t';cur=nt;}else{out+=' '.repeat(nv-cur);cur=nv;}}v=nv;i=j;continue;}
if(c==='\t'){out+=c;v+=tab-v%tab;i++;continue;}lead=false;out+=c;v+=c.charCodeAt(0)<0x300?1:cwOf(c.charCodeAt(0));i++;}return out;}
function tabsToSpaces(s){if(s.indexOf('\t')<0)return s;const tab=S.tabSize;let o='',v=0;for(const c of s){if(c==='\t'){const w=tab-v%tab;o+=' '.repeat(w);v+=w;}else{o+=c;v+=c.charCodeAt(0)<0x300?1:cwOf(c.charCodeAt(0));}}return o;}
/* bookmarks */
function bmLines(d){return[...d.bm].filter(l=>la-b);}
function bmJump(dir){const v=AV,d=v.doc;const ls=bmLines(d);if(!ls.length){flash('No bookmarks');return;}const cur=v.primSel().h.l;let t=dir>0?ls.find(l=>l>cur):[...ls].reverse().find(l=>l0?ls[0]:ls[ls.length-1];v.setSels([SEL(P(t,0))],{center:true});}
function bmText(d){return bmLines(d).map(l=>d.lines[l]).join('\n')+(d.bm.size?'\n':'');}
function bmRemove(d,keepMarked){const v=AV;lineOps((ls,a)=>ls.filter((s,i)=>keepMarked?d.bm.has(a+i):!d.bm.has(a+i)),{doc:true,type:'bm',msg:(o,n)=>`${o-n} line(s) removed`});if(!keepMarked)d.bm.clear();refreshDoc(d);}
/* style tokens */
function styleText(v){const s=v.primSel();if(!sEmpty(s))return getRange(v.doc,sFrom(s),sTo(s));const[a,b]=wordAtWord(v.doc.lines[s.h.l],s.h.c);return acmp(x.s,y.s));refreshDoc(d);}
function styleAll(n,one){const v=AV,d=v.doc;if(one){const ss=nonEmptySels(v);if(!ss.length){const t=styleText(v);if(!t)return;const p=v.primSel().h;const[a,b]=wordAtWord(d.lines[p.l],p.c);addMarks(d,[{s:P(p.l,a),e:P(p.l,b),st:n}]);return;}addMarks(d,ss.map(s=>({s:sFrom(s),e:sTo(s),st:n})));return;}
const t=styleText(v);if(!t){flash('Select some text first');return;}const sel=!sEmpty(v.primSel());const re=makeRE({text:t,mode:'normal',mcase:true,word:!sel||/^\w+$/.test(t)&&!sel});const L=[];
eachMatch(d,re,(a,b)=>{L.push({s:posOf(d,a),e:posOf(d,b),st:n});});d.marks=d.marks.filter(m=>m.st!==n||!L.some(x=>cmp(x.s,m.s)===0));addMarks(d,L);flash(`${L.length} occurrence(s) styled`);}
function clearStyle(n){const d=ad();d.marks=n==null?d.marks.filter(m=>m.st==='mf'&&false):d.marks.filter(m=>m.st!==n);refreshDoc(d);}
function jumpStyle(n,dir){const v=AV,d=v.doc,p=v.primSel();const ms=d.marks.filter(m=>n==null||m.st===n);if(!ms.length){flash('No styled text found');return;}let t;
if(dir>0)t=ms.find(m=>cmp(m.s,sTo(p))>=0&&cmp(m.s,sFrom(p))!==0)||ms[0];else t=[...ms].reverse().find(m=>cmp(m.s,sFrom(p))<0)||ms[ms.length-1];v.setSels([{a:t.s,h:t.e}],{center:true});}
function copyStyled(n){const d=ad();const t=d.marks.filter(m=>n==null||m.st===n).map(m=>getRange(d,m.s,m.e)).join(d.eol);if(!t){flash('No styled text found');return;}clipWrite(t);flash('Styled text copied');}
/* folding commands */
function foldCmd(kind,arg){const v=AV,d=v.doc;computeFolds(d);const l=v.primSel().h.l;
if(kind==='all'){for(const s of d.foldMap.keys())d.folded.add(s);}
else if(kind==='none'){d.folded.clear();d.manual.clear();}
else if(kind==='cur'){let best=null;for(const[s,e]of d.foldMap)if(s<=l&&l<=e&&(!best||s>best[0]))if(!d.folded.has(s)||s!==l)best=[s,e];if(!best){flash('No foldable block here');return;}d.folded.add(best[0]);refreshDoc(d);v.setSels([SEL(P(best[0],d.lines[best[0]].length))]);return;}
else if(kind==='uncur'){let hit=false;for(const[s,e]of d.foldMap)if(s<=l&&l<=e&&d.folded.has(s)){d.folded.delete(s);hit=true;}if(!hit)flash('Nothing to unfold');}
else if(kind==='level'){for(const[s]of d.foldMap)if((d.foldDepth.get(s)||0)===arg-1)d.folded.add(s);}
else if(kind==='unlevel'){for(const[s]of d.foldMap)if((d.foldDepth.get(s)||0)===arg-1)d.folded.delete(s);}
refreshDoc(d);const p=v.primSel().h;if(isHidden(d,p.l)){let k=p.l;while(k>0&&isHidden(d,k))k--;v.setSels([SEL(P(k,d.lines[k].length))]);}else v.schedule();}
function hideLines(){const v=AV,d=v.doc;const ls=v.selLines();let a=ls[0],b=ls[ls.length-1];if(a===0){a=1;}if(b Line