Skip to content

Fix for onEnter and onTab #24

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
KaiWilke opened this issue Mar 14, 2023 · 5 comments
Closed

Fix for onEnter and onTab #24

KaiWilke opened this issue Mar 14, 2023 · 5 comments
Assignees

Comments

@KaiWilke
Copy link

KaiWilke commented Mar 14, 2023

Hi WebCoder,

many thanks for your work. Your project inspired me very much and I finally created my own solution based on your original demo.

I've noticed that your onEnter and onTab functions causing annoying results when:

1.) Holding Enter till carrot floats out of the window (Chrome does not auto adjust scrollTop)
2.) Pressing Enter while scrollLeft is active and visible area is deep inside scrollLeft (it does not set scrollLeft to 0)
3.) Tabbing while selecting entire lines adds Tabs to lines outside of selectionStart/End.
4.) Tabbing on STRG+A does not adjust selectionStart accordingly.
5.) Shift-Tab on a given line does not remove leading tabs when carrot sits at the beginning of the line.

Feel free to integrate the Tab/Enter code I wrote for my solution. It also includes feature to convert leading space to tabs and remove intents from otherwise empty lines:

function onKeyEdit(id,evt) {
	let k=evt.key,sk=evt.shiftKey;
	if(k!='Enter'&&k!='Tab')return;
	evt.preventDefault();
	let s=id.selectionStart,e=id.selectionEnd,v=id.value,so=v.slice(0,s).split('\n').pop();
	if(k=='Enter') {
		id.setRangeText('\n'+'\t'.repeat(so.split(/[^\t]/)[0].length),s,e,'end'),id.scrollLeft=0;
		let p=id.value.slice(0,s+1).split('\n').length*15,t=id.scrollTop,h=id.clientHeight;
		if(p<t)id.scrollTop=p-30;
		else if(p>t+h)id.scrollTop=p-h;
		updateCode();
		return;
	};
	if(s==e&&!sk) {
		id.setRangeText('\t',s,s,'end');
	} else {
		let sr=sx=er=0,eo=se=-1,re=/([ ]{4}|[ ]{1,3}\t)/g,aR=[];
		for(let i=0,aL=v.split('\n');aR.length==0||e>eo+1;++i) {
			if(s>(eo+=1+aL[i].length))continue;
			let w=aL[i].split(/[^\t ]/);
			if(w.length==1) {
				++er,aR.push('');
				if(sx<1)++sr;
			} else {
				er=0,sx=1,aL[i]=w[0].replace(re,'\t')+aL[i].substr(w[0].length);
				if(sk) {
					if(aL[i].startsWith('\t'))aR.push(aL[i].substr(1));
					else aR.push(aL[i]);
				} else aR.push('\t'+aL[i]);
			};
		};
		id.setRangeText(aR.join('\n'),s-so.length,eo,'select');
		if(aR.join('')=='')se=v.slice(0,s).length-so.length;
		else if(s==e)se=s-(id.value.length==v.length||so==''?0:(1+(so.length-so.replace(re,'\t').length)));
		if(se>=0)id.selectionStart=id.selectionEnd=se;
		else id.selectionStart+=sr,id.selectionEnd-=er;
	};
	updateCode();
};

Note: The convert of leading space to tabs is hardcoded for Tab-Size:4 and the Enter scroll adjustments are based on a line-height of hardcoded 15px.

Update: Slightly modified the provided code to include adjustment of selectionStart for empty lines on multi line selections. That was somehow left over. But I guess that there is no such perfect TAB sequence, since mileage may vary... .

Thanks and Cheers, Kai

@WebCoder49
Copy link
Owner

Thank you very much for this; it appears to solve so many problems! I will look at it and integrate it into the repo when I have time.

@KaiWilke
Copy link
Author

You are very much welcome. Cheers, Kai

@WebCoder49 WebCoder49 self-assigned this Mar 16, 2023
@KaiWilke
Copy link
Author

Hi WebCoder,

i just changed my solution to use the (slightly depreciated) execCommand() approach. Compared to the previously used setRangeText() apporach, the benefit is that the UNDO/REDO buffer remains accessible after TAB/Enter sequences.

Also added detection for computed Line-Heigh so that you don't have to use any hard coded values.

Note: The execCommand() simulates a 'oninput' change, so you don't have to call updateCode() manually inside the function. The edit->view update will be handled by the textarea 'oninput' event without any further adjustments.

function onKeyEdit(id,evt) {
	let k=evt.key,sk=evt.shiftKey;
	if(k!='Enter'&&k!='Tab')return;
	evt.preventDefault();
	let m='insertText',s=id.selectionStart,e=id.selectionEnd,t=id.scrollTop,v=id.value,so=v.slice(0,s).split('\n').pop();
	if(k=='Enter') {
		document.execCommand(m,false,'\n'+'\t'.repeat(so.split(/[^\t]/)[0].length));
		id.scrollLeft=0;
		let h=id.clientHeight,l=getComputedStyle(id).lineHeight.slice(0,-2),p=id.value.slice(0,s+1).split('\n').length*l;
		if(p<t+l)t=p-2*l;
		else if(p>t+h)t=p-h;
		id.scrollTop=t;
		return;
	};
	let r='\t',sr=er=0,aR=[];
	if(s==e&&sk) {
		m='delete',so=v.slice(s-1,s);
		if((s==0||so=='\n')&&v.slice(s,s+1)=='\t')id.selectionStart+=1;
		else if(so!='\t')return;
	} else if(s!=e) {
		let eo=-1;
		for(let i=sx=0,aL=v.split('\n');aR.length==0||e>eo+1;++i) {
			if(s>(eo+=1+aL[i].length))continue;
			let w=aL[i].split(/[^\t ]/);
			if(w.length==1) {
				++er,aR.push('');
				if(sx<1)++sr;
			} else {
				er=0,sx=1,aL[i]=w[0].replace(/([ ]{4}|[ ]{1,3}\t)/g,'\t')+aL[i].substr(w[0].length);
				if(sk) {
					if(aL[i].startsWith('\t'))aR.push(aL[i].substr(1));
					else aR.push(aL[i]);
				} else aR.push('\t'+aL[i]);
			};
		};
		r=aR.join('\n');
		id.selectionStart=(so=s-so.length),id.selectionEnd=eo;
	};
	document.execCommand(m,false,r);
	if(s!=e&&aR.join('')!='')id.selectionStart=so+sr,id.selectionEnd=so+r.length-er,id.scrollTop=t;
};

Cheers, Kai

@WebCoder49
Copy link
Owner

I'm afraid that I cannot implement this code because it does not appear to fix the issues in Microsoft Edge v111.0 or Google Chrome v111.0, but it does work in Firefox. Please let me know if I am missing something.
I will continue to address the issues you have outlined - thank you for the execCommand suggestion.

WebCoder49 pushed a commit that referenced this issue Apr 5, 2023
@WebCoder49
Copy link
Owner

I have released this in version 1.2.4.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants