Highlight single-line comments, and refactor highlighting method
All checks were successful
timw4mail/scroll/pipeline/head This commit looks good

This commit is contained in:
Timothy Warren 2024-07-16 17:18:09 -04:00
parent 32e3676b02
commit 359e739fe8
3 changed files with 124 additions and 67 deletions

View File

@ -1,4 +1,5 @@
import { node_path as path } from '../runtime/mod.ts';
import Option, { None, Some } from '../option.ts';
// ----------------------------------------------------------------------------
// File-related types
@ -18,6 +19,7 @@ export interface HighlightingOptions {
interface IFileType {
readonly name: FileLang;
readonly singleLineComment: Option<string>;
readonly hlOptions: HighlightingOptions;
get flags(): HighlightingOptions;
}
@ -27,6 +29,7 @@ interface IFileType {
*/
export abstract class AbstractFileType implements IFileType {
public readonly name: FileLang = FileLang.Plain;
public readonly singleLineComment = None;
public readonly hlOptions: HighlightingOptions = {
numbers: false,
strings: false,
@ -47,6 +50,7 @@ const defaultHighlightOptions: HighlightingOptions = {
class TypeScriptFile extends AbstractFileType {
public readonly name: FileLang = FileLang.TypeScript;
public readonly singleLineComment = Some('//');
public readonly hlOptions: HighlightingOptions = {
...defaultHighlightOptions,
};
@ -54,6 +58,7 @@ class TypeScriptFile extends AbstractFileType {
class JavaScriptFile extends AbstractFileType {
public readonly name: FileLang = FileLang.JavaScript;
public readonly singleLineComment = Some('//');
public readonly hlOptions: HighlightingOptions = {
...defaultHighlightOptions,
};
@ -61,6 +66,7 @@ class JavaScriptFile extends AbstractFileType {
class CSSFile extends AbstractFileType {
public readonly name: FileLang = FileLang.CSS;
public readonly singleLineComment = None;
public readonly hlOptions: HighlightingOptions = {
...defaultHighlightOptions,
};

View File

@ -5,6 +5,7 @@ export enum HighlightType {
Number,
Match,
String,
SingleLineComment,
}
export function highlightToColor(type: HighlightType): string {
@ -18,6 +19,9 @@ export function highlightToColor(type: HighlightType): string {
case HighlightType.String:
return Ansi.color256(201);
case HighlightType.SingleLineComment:
return Ansi.color256(45);
default:
return Ansi.ResetFormatting;
}

View File

@ -225,94 +225,141 @@ export class Row {
}
public highlight(word: Option<string>, syntax: FileType): void {
const highlighting = [];
this.hl = [];
let i = 0;
for (; i < this.rsize;) {
const ch = this.rchars[i];
const maybeNext = this.highlightComment(i, syntax, ch)
.orElse(() => this.highlightString(i, syntax, ch))
.orElse(() => this.highlightNumber(i, syntax, ch));
if (maybeNext.isSome()) {
const next = maybeNext.unwrap();
if (next < this.rsize) {
i = maybeNext.unwrap();
continue;
}
break;
}
this.hl.push(HighlightType.None);
i += 1;
}
this.highlightMatch(word);
}
protected highlightMatch(word: Option<string>): void {
let searchIndex = 0;
const matches = [];
// Find matches for the current search
if (word.isSome()) {
while (true) {
const match = this.find(word.unwrap(), searchIndex);
const match = this.find(
word.unwrap(),
searchIndex,
SearchDirection.Forward,
);
if (match.isNone()) {
break;
}
matches.push(match.unwrap());
const nextPossible = match.unwrap() + strlen(word.unwrap());
const index = match.unwrap();
const nextPossible = index + strlen(word.unwrap());
if (nextPossible < this.rsize) {
let i = index;
for (const _ in strChars(word.unwrap())) {
this.hl[i] = HighlightType.Match;
i += 1;
}
searchIndex = nextPossible;
} else {
break;
}
}
}
}
let prevIsSeparator = true;
let inString: string | boolean = false;
let i = 0;
for (; i < this.rsize;) {
const ch = this.rchars[i];
const prevHighlight = (i > 0) ? highlighting[i - 1] : HighlightType.None;
// Highlight search matches
if (word.isSome()) {
if (matches.includes(i)) {
for (const _ in strChars(word.unwrap())) {
i += 1;
highlighting.push(HighlightType.Match);
}
continue;
protected highlightComment(
i: number,
syntax: FileType,
_ch: string,
): Option<number> {
// Highlight single-line comments
if (syntax.singleLineComment.isSome()) {
const commentStart = syntax.singleLineComment.unwrap();
if (
this.toString().indexOf(commentStart) === this.charIndexToByteIndex(i)
) {
for (; i < this.rsize; i++) {
this.hl.push(HighlightType.SingleLineComment);
}
return Some(i);
}
// Highlight strings
if (syntax.flags.strings) {
if (inString) {
highlighting.push(HighlightType.String);
// Handle escaped characters in strings
if (ch === '\\' && i < this.rsize + 1) {
highlighting.push(HighlightType.String);
i += 2;
continue;
}
if (ch === inString) {
inString = false;
}
i += 1;
prevIsSeparator = true;
continue;
} else if (prevIsSeparator && ch === '"' || ch === "'") {
highlighting.push(HighlightType.String);
inString = ch;
prevIsSeparator = true;
i += 1;
continue;
}
}
// Highlight numbers
if (syntax.flags.numbers) {
const isNumeric = isAsciiDigit(ch) && (prevIsSeparator ||
prevHighlight === HighlightType.Number);
const isDecimalNumeric = ch === '.' &&
prevHighlight === HighlightType.Number;
const isHexNumeric = ch === 'x' &&
prevHighlight === HighlightType.Number;
if (isNumeric || isDecimalNumeric || isHexNumeric) {
highlighting.push(HighlightType.Number);
} else {
highlighting.push(HighlightType.None);
}
}
prevIsSeparator = isSeparator(ch);
i += 1;
}
this.hl = highlighting;
return None;
}
protected highlightString(
i: number,
syntax: FileType,
ch: string,
): Option<number> {
// Highlight strings
if (syntax.flags.strings && ch === '"' || ch === "'") {
while (true) {
this.hl.push(HighlightType.String);
i += 1;
if (i === this.rsize) {
break;
}
const nextChar = this.rchars[i];
if (nextChar === ch) {
break;
}
}
this.hl.push(HighlightType.String);
i += 1;
return Some(i);
}
return None;
}
protected highlightNumber(
i: number,
syntax: FileType,
ch: string,
): Option<number> {
// Highlight numbers
if (syntax.flags.numbers && isAsciiDigit(ch)) {
if (i > 0 && !isSeparator(this.rchars[i - 1])) {
return None;
}
while (true) {
this.hl.push(HighlightType.Number);
i += 1;
if (i < this.rsize) {
const nextChar = this.rchars[i];
if (nextChar !== '.' && nextChar !== 'x' && !isAsciiDigit(nextChar)) {
break;
}
} else {
break;
}
}
return Some(i);
}
return None;
}
public render(offset: number, len: number): string {