I have a <textarea>
element as in the code below. How can I display line numbers along the left margin of it?
<TEXTAREA name="program" id="program" rows="15" cols="65" ></TEXTAREA>
I have a <textarea>
element as in the code below. How can I display line numbers along the left margin of it?
<TEXTAREA name="program" id="program" rows="15" cols="65" ></TEXTAREA>
This is a very simple, but effective trick. It inserts an image with the line numbers already added.
The only catch is you may need to create your own image to match your UI design.
textarea.numbered {
background: url(http://i.imgur.com/2cOaJ.png);
background-attachment: local;
background-repeat: no-repeat;
padding-left: 35px;
padding-top: 10px;
border-color:#ccc;
}
<textarea cols="50" rows="10" class="numbered"></textarea>
Credit goes to: Aakash Chakravarthy
1500
–
Fourteen font-family: 'Courier New'!important; font-size:0.667rem!important;
tried up to 500 lines, lined up nicely. ps bootstrap 5 –
Cuckooflower Someone else here recommended CodeMirror, and I can't hardly recommend it enough! But this answer didn't really provide any technical details.
Other solutions: Everything else I tried here has problems with line numbers not matching up with lines. I believe this is because I have monitor DPI (dots per inch) at 120%, and these solutions didn't take this into account.
So, how do you use CodeMirror??? Easy! Just look at the 21,000 words of the documentation! I hope to explain 99% of your questions on it in less than page or two.
100% working demo, and it's working perfectly in the StackOverflow sandbox:
var editor = CodeMirror.fromTextArea(document.getElementById('code'), {
lineNumbers: true,
mode: 'text/x-perl',
theme: 'abbott',
});
<script language="javascript" type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/codemirror.min.js"></script>
<script language="javascript" type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/mode/perl/perl.min.js"></script>
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/codemirror.min.css"></link>
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/theme/abbott.min.css"></link>
<textarea id="code" name="code">
if($cool_variable) {
doTheCoolThing(); # it's PRETTY cool, imho
}</textarea>
Add this to your <head>
block...
<script language="javascript" type="text/javascript" src="/static/js/codemirror-5.62.0/lib/codemirror.js"></script>
<link rel="stylesheet" type="text/css" href="/static/js/codemirror-5.62.0/lib/codemirror.css"></link>
And, if you like to have extra-bracket color matching, also load this:
<script language="javascript" type="text/javascript" src="/codemirror-5.62.0/addon/edit/matchbrackets.js"></script>
Check the /codemirror-5.62.0/mode/
dir to see what language matches the language you'll be coding in. There is extensive support in this area.
Add this to your <head>
block...
<script language="javascript" type="text/javascript" src="/static/js/codemirror-5.62.0/mode/perl/perl.js"></script>
Have some textarea to use....
<textarea id="code" name="code"></textarea>
Initialize and set your codemirror in the JS. You need to use the Mimetype to indicate the mode you want to use, in this case, I'm indicating the Perl Mimetype...
var editor = CodeMirror.fromTextArea(document.getElementById('code'), {
lineNumbers: true,
mode: 'text/x-perl',
matchBrackets: true,
});
Choose some theme you like, 'liquibyte'
, 'cobalt'
and 'abbott'
are both pretty decent dark-mode-ish themes. Run this after defining editor
...
editor.setOption('theme', 'cobalt');
And that's it!
simplemode
plugin if the OP wants to display something other than the supported languages. –
Aphelion No one tried to do this using HTML5 Canvas object and by painting line numbers on it. So I've managed to put canvas and textarea, one next to the other, and painted numbers on canvas.
//
// desc: demonstrates textarea line numbers using canvas paint
// auth: nikola bozovic <nigerija@gmail>
//
var TextAreaLineNumbersWithCanvas = function() {
var div = document.getElementById('wrapper');
var cssTable = 'padding:0px 0px 0px 0px!important; margin:0px 0px 0px 0px!important; font-size:1px;line-height:0px; width:auto;';
var cssTd1 = 'border:1px #345 solid; border-right:0px; vertical-align:top; width:1px; background: #303030';
var cssTd2 = 'border:1px #345 solid; border-left:0px; vertical-align:top;';
var cssButton = 'width:120px; height:40px; border:1px solid #333 !important; border-bottom-color: #484!important; color:#ffe; background-color:#222;';
var cssCanvas = 'border:0px; background-color:#1c1c20; margin-top:0px; padding-top:0px;';
// LAYOUT (table 2 panels)
var table = document.createElement('table');
table.setAttribute('cellspacing', '0');
table.setAttribute('cellpadding', '0');
table.setAttribute('style', cssTable);
var tr = document.createElement('tr');
var td1 = document.createElement('td');
td1.setAttribute('style', cssTd1);
var td2 = document.createElement('td');
td2.setAttribute('style', cssTd2);
tr.appendChild(td1);
tr.appendChild(td2);
table.appendChild(tr);
// TEXTAREA
var ta = this.evalnode = document.getElementById('mytextarea');
// TEXTAREA NUMBERS (Canvas)
var canvas = document.createElement('canvas');
canvas.width = 48; // must not set width & height in css !!!
canvas.height = 500; // must not set width & height in css !!!
canvas.setAttribute('style', cssCanvas);
ta.canvasLines = canvas;
td1.appendChild(canvas);
td2.appendChild(ta);
div.appendChild(table);
// PAINT LINE NUMBERS
ta.paintLineNumbers = function() {
try {
var canvas = this.canvasLines;
if (canvas.height != this.clientHeight) canvas.height = this.clientHeight; // on resize
var ctx = canvas.getContext("2d");
ctx.fillStyle = "#303030";
ctx.fillRect(0, 0, 42, this.scrollHeight + 1);
ctx.fillStyle = "#808080";
ctx.font = "11px monospace"; // NOTICE: must match TextArea font-size(11px) and lineheight(15) !!!
var startIndex = Math.floor(this.scrollTop / 15, 0);
var endIndex = startIndex + Math.ceil(this.clientHeight / 15, 0);
for (var i = startIndex; i < endIndex; i++) {
var ph = 10 - this.scrollTop + (i * 15);
var text = '' + (1 + i); // line number
ctx.fillText(text, 40 - (text.length * 6), ph);
}
} catch (e) {
alert(e);
}
};
ta.onscroll = function(ev) {
this.paintLineNumbers();
};
ta.onmousedown = function(ev) {
this.mouseisdown = true;
}
ta.onmouseup = function(ev) {
this.mouseisdown = false;
this.paintLineNumbers();
};
ta.onmousemove = function(ev) {
if (this.mouseisdown) this.paintLineNumbers();
};
// make sure it's painted
ta.paintLineNumbers();
return ta;
};
var ta = TextAreaLineNumbersWithCanvas();
ta.value = TextAreaLineNumbersWithCanvas.toString();
#mytextarea {
width: auto;
height: 500px;
font-size: 11px;
font-family: monospace;
line-height: 15px;
font-weight: 500;
margin: 0;
padding: 0;
resize: both;
color: #ffa;
border: 0;
background-color: #222;
white-space: pre;
overflow: auto;
}
/* supported only in opera */
#mytextarea {
scrollbar-arrow-color: #ee8;
scrollbar-base-color: #444;
scrollbar-track-color: #666;
scrollbar-face-color: #444;
/* outer light */
scrollbar-3dlight-color: #444;
/* inner light */
scrollbar-highlight-color: #666;
/* outer dark */
scrollbar-darkshadow-color: #444;
/* inner dark */
scrollbar-shadow-color: #222;
}
/* chrome scrollbars */
textarea::-webkit-scrollbar {
width: 16px;
background-color: #444;
cursor: pointer;
}
textarea::-webkit-scrollbar-track {
background-color: #333;
cursor: pointer;
}
textarea::-webkit-scrollbar-corner {
background-color: #484;
-webkit-box-shadow: inset 0 0 6px rgba(255, 255, 255, 0.3);
}
textarea::-webkit-scrollbar-thumb {
background-color: #444;
-webkit-box-shadow: inset 0 0 6px rgba(255, 255, 255, 0.3);
cursor: pointer;
}
<div id="wrapper">
<textarea id="mytextarea" cols="80" rows="10"></textarea>
</div>
There is a limitation in that we can't handle word-wrap
easily in the Paint()
function without iterating the entire textarea content and drawing to a hidden object for measurements of each line height, which would yield very complex code.
const textarea = document.querySelector("textarea");
const numbers = document.querySelector(".numbers");
textarea.addEventListener("keyup", (e) => {
const num = e.target.value.split("\n").length;
numbers.innerHTML = Array(num).fill("<span></span>").join("");
});
textarea.addEventListener("keydown", (event) => {
if (event.key === "Tab") {
const start = textarea.selectionStart;
const end = textarea.selectionEnd;
textarea.value =
textarea.value.substring(0, start) +
"\t" +
textarea.value.substring(end);
event.preventDefault();
}
});
body {
font-family: Consolas, "Courier New", Courier, monospace;
}
.editor {
display: inline-flex;
gap: 10px;
font-family: Consolas, "Courier New", Courier, monospace;
line-height: 21px;
background-color: #282a3a;
border-radius: 2px;
padding: 20px 10px;
}
textarea {
line-height: 21px;
overflow-y: hidden;
padding: 0;
border: 0;
background: #282a3a;
color: #fff;
min-width: 500px;
outline: none;
resize: none;
font-family: Consolas, "Courier New", Courier, monospace;
}
.numbers {
width: 20px;
text-align: right;
}
.numbers span {
counter-increment: linenumber;
}
.numbers span::before {
content: counter(linenumber);
display: block;
color: #506882;
}
<div class="editor">
<div class="numbers">
<span></span>
</div>
<textarea cols="30" rows="10"></textarea>
</div>
It actually works quite well.
The line numbers do not occur instantly but it workds quite fast.
Consider the use of a contenteditable
ordered list <ol>
instead of <textarea>
ol {
font-family: monospace;
white-space: pre;
}
li::marker {
font-size: 10px;
color: grey;
}
<ol contenteditable><li>lorem ipsum
<li>>> lorem ipsum
<li>lorem ipsum,\
<li>lorem ipsum.
<li>>> lorem ipsum
<li>lorem ipsum
<li>lorem ipsum
<li>lorem
<li>ipsum
<li>>> lorem ipsum
<li>lorem ipsum
</ol>
However, ::marker
styling seems limited (list-style-type
). E.g. removing the period or vertical-align: super
seems to needs other workarounds (back to li:before
and counter
).
Bonus: <li>
also does not need the closing tag </li>
(https://html.spec.whatwg.org/multipage/syntax.html#optional-tags), which saves typing.
Also as far as I understand, the <textarea>
in codemirror just works in the background (Pseudo contenteditable: how does codemirror works?).
I've created a line numbering system that works good on textarea with line wrapped. I haven't customized it for single line overflowing code, but it's good if you want wrapped lines.
'use scrict';
var linenumbers = document.getElementById('line-numbers');
var editor = document.getElementById('codeblock');
function getWidth(elem) {
return elem.scrollWidth - (parseFloat(window.getComputedStyle(elem, null).getPropertyValue('padding-left')) + parseFloat(window.getComputedStyle(elem, null).getPropertyValue('padding-right')))
}
function getFontSize(elem) {
return parseFloat(window.getComputedStyle(elem, null).getPropertyValue('font-size'));
}
function cutLines(lines) {
return lines.split(/\r?\n/);
}
function getLineHeight(elem) {
var computedStyle = window.getComputedStyle(elem);
var lineHeight = computedStyle.getPropertyValue('line-height');
var lineheight;
if (lineHeight === 'normal') {
var fontSize = computedStyle.getPropertyValue('font-size');
lineheight = parseFloat(fontSize) * 1.2;
} else {
lineheight = parseFloat(lineHeight);
}
return lineheight;
}
function getTotalLineSize(size, line, options) {
if (typeof options === 'object') options = {};
var p = document.createElement('span');
p.style.setProperty('white-space', 'pre');
p.style.display = 'inline-block';
if (typeof options.fontSize !== 'undefined') p.style.fontSize = options.fontSize;
p.innerHTML = line;
document.body.appendChild(p);
var result = (p.scrollWidth / size);
p.remove();
return Math.ceil(result);
}
function getLineNumber() {
var textLines = editor.value.substr(0, editor.selectionStart).split("\n");
var currentLineNumber = textLines.length;
var currentColumnIndex = textLines[textLines.length-1].length;
return currentLineNumber;
}
function init() {
var totallines = cutLines(editor.value), linesize;
linenumbers.innerHTML = '';
for (var i = 1; i <= totallines.length; i++) {
var num = document.createElement('p');
num.innerHTML = i;
linenumbers.appendChild(num);
linesize = getTotalLineSize(getWidth(editor), totallines[(i - 1)], {'fontSize' : getFontSize(editor)});
if (linesize > 1) {
num.style.height = (linesize * getLineHeight(editor)) + 'px';
}
}
linesize = getTotalLineSize(getWidth(editor), totallines[(getLineNumber() - 1)], {'fontSize' : getFontSize(editor)});
if (linesize > 1) {
linenumbers.childNodes[(getLineNumber() - 1)].style.height = (linesize * getLineHeight(editor)) + 'px';
}
editor.style.height = editor.scrollHeight;
linenumbers.style.height = editor.scrollHeight;
}
editor.addEventListener('keyup', init);
editor.addEventListener('input', init);
editor.addEventListener('click', init);
editor.addEventListener('paste', init);
editor.addEventListener('load', init);
editor.addEventListener('mouseover', init);
#source-code {
width: 100%;
height: 450px;
background-color: #2F2F2F;
display: flex;
justify-content: space-between;
overflow-y: scroll;
border-radius: 10px;
}
#source-code * {
box-sizing: border-box;
}
#codeblock {
white-space: pre-wrap;
width: calc(100% - 30px);
float: right;
height: auto;
font-family: arial;
color: #fff;
background: transparent;
padding: 15px;
line-height: 30px;
overflow: hidden;
min-height: 100%;
border: none;
}
#line-numbers {
min-width: 30px;
height: 100%;
padding: 15px 5px;
font-size: 14px;
vertical-align: middle;
text-align: right;
margin: 0;
color: #fff;
background: black;
}
#line-numbers p {
display: block;
height: 30px;
line-height: 30px;
margin: 0;
}
#codeblock:focus{
outline: none;
}
<div id="source-code">
<div id="line-numbers"><p>1</p></div>
<textarea id="codeblock"></textarea>
</div>
function generateWithNumber() {
let inputTexts = document.getElementById("input").value
let textsByLine = inputTexts.split("\n");
const listMarkup = makeUL(textsByLine);
document.getElementById("output").appendChild(listMarkup);
}
function makeUL(array) {
let list = document.createElement('ol');
for (let i = 0; i < array.length; i++) {
let item = document.createElement('li');
item.appendChild(document.createTextNode(array[i]));
list.appendChild(item);
}
return list;
}
// document.getElementById('foo').appendChild(makeUL(options[0]));
ol {
counter-reset: list;
}
ol > li {
list-style: none;
}
ol > li:before {
content: counter(list) ") ";
counter-increment: list;
}
<textarea id="input"></textarea>
<button onClick=generateWithNumber() >Generate</button>
<p id="output"></p>
This code defines a function generateWithNumber()
that is triggered when a button with the onClick
event is clicked. The purpose of this function is to take input text from a <textarea>
element, split it into lines, and display the lines as a numbered list in an <ol>
(ordered list) element.
© 2022 - 2024 — McMap. All rights reserved.