Skip to Content
  • Website
Codsen
  • Home
  • Open Source
  • Articles
  • About

prevOpen Source→line-column-mininext

line-column-mini

Convert string index to line-column position

Downloads per monthMIT Licenselibera manifesto
  • the top
  • IDEA
  • API — LINECOL()
  • API — GETLINESTARTI…
  • CUTTING CORN…
  • SKIPPING CHEC…

Idea

The existing index-to line/column conversion program, popular and fast line-columnopens in a new tab has many disadvantages:

  • Didn’t support CR (old Macopens in a new tab) line endings, only CRLF (Windows) and LF (modern Mac)
  • Didn’t include type definitions natively…
  • ... and counterpart definitions on @types/line-column were wrongopens in a new tab (notice the missing LineColumnInfo export)
  • It was converting both ways, index-to line/column and line/column-to index, which, the source being not in ES modules, meant the unused functions would end up bundled too

No support for CR line endings was a blocker because we will use this program on emlint which is meant to catch such things.

This program is an alternative that only converts string index position to line/column number. It’s as fast as line-columnopens in a new tab.

API — lineCol()

The main function lineCol() is imported like this:

It’s a function which takes three input arguments:

Input argumentTypeObligatoryDescription
input
Type: string or number array
Obligatory: yes
inputstring or number arrayyesEither a string or a result of getLineStartIndexes()
idx
Type: natural number or zero (a string index)
Obligatory: yes
idxnatural number or zero (a string index)yesA string index to convert
skipChecks
Type: boolean
Obligatory: no
skipChecksbooleannoSet it to true to skip all checks to increase perf even more

The function returns a null or a plain object like:

{
  line: 4,
  col: 3
}

Pre-calculating the line indexes using getLineStartIndexes() increases performance on subsequent calls — the program doesn’t need to perform the first half of the calculations, finding each line start indexes.

API — getLineStartIndexes()

The main function getLineStartIndexes() is imported like this:

It’s a function which takes three input arguments:

In other words, it’s a function which takes three input arguments, third-one optional:

Input argumentTypeObligatoryDescription
input
Type: string
Obligatory: yes
inputstringyesThe source string

The function returns an array of numbers, all string line start indexes, for example:

[0, 4, 6, 9, 12];

Each number means, there are that many characters before the particular line’s first character.

Feed this array to lineCol() instead of an input string.

Cutting corners

The algorithm works by first extracting an array of each line start indexes, then it performs a search on that array, searching where your given index does slot in.

If you calculate that indexes array once, you can reuse it in multiple calls (as long as the string is the same), improving the performance by a magnitude.

import { lineCol, getLineStartIndexes } from "line-column-mini";
import { strict as assert } from "assert";
const lineIndexes = getLineStartIndexes("abc\ndef\r\nghi\njkl");
// each call to lineCol() will cut corners:
assert.deepEqual(lineCol(lineIndexes, 14), {
  line: 4,
  col: 2,
});
assert.deepEqual(lineCol(lineIndexes, 15), {
  line: 4,
  col: 3,
});

// the full calculation would be equivalent but slower
assert.deepEqual(lineCol("abc\ndef\r\nghi\njkl", 15), {
  line: 4,
  col: 3,
});

Skipping checks

By the time you convert indexes to line/column position, you probably have done all the checks, you can guarantee that input string is not empty and so on. In those cases, you can force this program to skip the input validation checks by passing third argument as true:

import { lineCol, getLineStartIndexes } from "line-column-mini";
import { strict as assert } from "assert";
const input = "abc\ndef\r\nghi\rjkl";
assert.deepEqual(
  lineCol(
    input,
    5,
    true, // <-------- skips all validation checks
  ),
  {
    line: 2,
    col: 2,
  },
);

That should make the program run a few percent faster.

↑ back to top
prev next

Copyright

All rights reserved © Roy Revelt 2026
All our open source packages are under MIT licenceopens in a new tab

Activities

🐛 See a bug? Raise an issueopens in a new tab
💘 Check out the Indiewebopens in a new tab and Libera manifestoopens in a new tab