A Wikiful of Hacks: Hacks.Wiki is an experiment to organise quick hacks, notes, bookmarks and tools into an easy-to-build-and-maintain “Digital Garden”.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

0 lines
39 KiB

  1. import{Annotation,Facet,combineConfig,StateField,EditorSelection,Transaction,ChangeSet,ChangeDesc,StateEffect,Text,findClusterBreak,countColumn,CharCategory}from"@codemirror/state";import{EditorView,Direction}from"@codemirror/view";import{IndentContext,getIndentation,indentString,indentUnit,getIndentUnit,matchBrackets,syntaxTree}from"@codemirror/language";import{NodeProp}from"@lezer/common";const toggleComment=target=>{let config=getConfig(target.state);return config.line?toggleLineComment(target):config.block?toggleBlockCommentByLine(target):false};function command(f,option){return({state,dispatch})=>{if(state.readOnly)return false;let tr=f(option,state);if(!tr)return false;dispatch(state.update(tr));return true}}const toggleLineComment=command(changeLineComment,0);const lineComment=command(changeLineComment,1);const lineUncomment=command(changeLineComment,2);const toggleBlockComment=command(changeBlockComment,0);const blockComment=command(changeBlockComment,1);const blockUncomment=command(changeBlockComment,2);const toggleBlockCommentByLine=command((o,s)=>changeBlockComment(o,s,selectedLineRanges(s)),0);function getConfig(state,pos=state.selection.main.head){let data=state.languageDataAt("commentTokens",pos);return data.length?data[0]:{}}const SearchMargin=50;function findBlockComment(state,{open,close},from,to){let textBefore=state.sliceDoc(from-SearchMargin,from);let textAfter=state.sliceDoc(to,to+SearchMargin);let spaceBefore=/\s*$/.exec(textBefore)[0].length,spaceAfter=/^\s*/.exec(textAfter)[0].length;let beforeOff=textBefore.length-spaceBefore;if(textBefore.slice(beforeOff-open.length,beforeOff)==open&&textAfter.slice(spaceAfter,spaceAfter+close.length)==close){return{open:{pos:from-spaceBefore,margin:spaceBefore&&1},close:{pos:to+spaceAfter,margin:spaceAfter&&1}}}let startText,endText;if(to-from<=2*SearchMargin){startText=endText=state.sliceDoc(from,to)}else{startText=state.sliceDoc(from,from+SearchMargin);endText=state.sliceDoc(to-SearchMargin,to)}let startSpace=/^\s*/.exec(startText)[0].length,endSpace=/\s*$/.exec(endText)[0].length;let endOff=endText.length-endSpace-close.length;if(startText.slice(startSpace,startSpace+open.length)==open&&endText.slice(endOff,endOff+close.length)==close){return{open:{pos:from+startSpace+open.length,margin:/\s/.test(startText.charAt(startSpace+open.length))?1:0},close:{pos:to-endSpace-close.length,margin:/\s/.test(endText.charAt(endOff-1))?1:0}}}return null}function selectedLineRanges(state){let ranges=[];for(let r of state.selection.ranges){let fromLine=state.doc.lineAt(r.from);let toLine=r.to<=fromLine.to?fromLine:state.doc.lineAt(r.to);let last=ranges.length-1;if(last>=0&&ranges[last].to>fromLine.from)ranges[last].to=toLine.to;else ranges.push({from:fromLine.from,to:toLine.to})}return ranges}function changeBlockComment(option,state,ranges=state.selection.ranges){let tokens=ranges.map(r=>getConfig(state,r.from).block);if(!tokens.every(c=>c))return null;let comments=ranges.map((r,i)=>findBlockComment(state,tokens[i],r.from,r.to));if(option!=2&&!comments.every(c=>c)){return{changes:state.changes(ranges.map((range,i)=>{if(comments[i])return[];return[{from:range.from,insert:tokens[i].open+" "},{from:range.to,insert:" "+tokens[i].close}]}))}}else if(option!=1&&comments.some(c=>c)){let changes=[];for(let i=0,comment;i<comments.length;i++)if(comment=comments[i]){let token=tokens[i],{open,close}=comment;changes.push({from:open.pos-token.open.length,to:open.pos+open.margin},{from:close.pos-close.margin,to:close.pos+token.close.length})}return{changes:changes}}return null}function changeLineComment(option,state,ranges=state.selection.ranges){let lines=[];let prevLine=-1;for(let{from,to}of ranges){let startI=lines.length,minIndent=1e9;for(let pos=from;pos<=to;){let line=state.doc.lineAt(pos);if(line.from>prevLine&&(from==to||to>line.from)){prevLine=line.from;let token=getConfig(state,pos).line;if(!token)continue;let indent=/^\s*/.exec(line.text)[0].length;let empty=indent==line.length;let comment=line.text.slice(indent,indent+token.length)==token?indent:-1;if(indent<line.text.length&&i