+
+ protected measureTextSize(): {x: number; y: number} {
+ if (this.textSizeCache) {
+ return this.textSizeCache;
+ }
+
+ const el = document.createElement("div");
+ el.textContent = `a\nb`;
+ const inputStyles = window.getComputedStyle(this.input)
+ el.style.font = inputStyles.font;
+ el.style.lineHeight = inputStyles.lineHeight;
+ el.style.padding = '0px';
+ el.style.display = 'inline-block';
+ el.style.visibility = 'hidden';
+ el.style.position = 'absolute';
+ el.style.whiteSpace = 'pre';
+ this.input.after(el);
+
+ const bounds = el.getBoundingClientRect();
+ el.remove();
+ this.textSizeCache = {
+ x: bounds.width,
+ y: bounds.height / 2,
+ };
+ return this.textSizeCache;
+ }
+
+ protected measureLineCharCount(textWidth: number): number {
+ const inputStyles = window.getComputedStyle(this.input);
+ const paddingLeft = Number(inputStyles.paddingLeft.replace('px', ''));
+ const paddingRight = Number(inputStyles.paddingRight.replace('px', ''));
+ const width = Number(inputStyles.width.replace('px', ''));
+ const textSpace = width - (paddingLeft + paddingRight);
+
+ return Math.floor(textSpace / textWidth);
+ }
+
+ protected mouseEventToTextRelativeCoords(event: MouseEvent): {x: number; y: number} {
+ const inputBounds = this.input.getBoundingClientRect();
+ const inputStyles = window.getComputedStyle(this.input);
+ const paddingTop = Number(inputStyles.paddingTop.replace('px', ''));
+ const paddingLeft = Number(inputStyles.paddingLeft.replace('px', ''));
+
+ const xPos = Math.max(event.clientX - (inputBounds.left + paddingLeft), 0);
+ const yPos = Math.max((event.clientY - (inputBounds.top + paddingTop)) + this.input.scrollTop, 0);
+
+ return {x: xPos, y: yPos};
+ }
+
+ protected inputPositionToSelection(x: number, y: number): MarkdownEditorInputSelection {
+ const textSize = this.measureTextSize();
+ const lineWidth = this.measureLineCharCount(textSize.x);
+
+ const lines = this.getText().split('\n');
+
+ let currY = 0;
+ let currPos = 0;
+ for (const line of lines) {
+ let linePos = 0;
+ const wrapCount = Math.max(Math.ceil(line.length / lineWidth), 1);
+ for (let i = 0; i < wrapCount; i++) {
+ currY += textSize.y;
+ if (currY > y) {
+ const targetX = Math.floor(x / textSize.x);
+ const maxPos = Math.min(currPos + linePos + targetX, currPos + line.length);
+ return {from: maxPos, to: maxPos};
+ }
+
+ linePos += lineWidth;
+ }
+
+ currPos += line.length + 1;
+ }
+
+ return this.getSelection();
+ }