Highlight single-line comments, and refactor highlighting method
All checks were successful
timw4mail/scroll/pipeline/head This commit looks good
All checks were successful
timw4mail/scroll/pipeline/head This commit looks good
This commit is contained in:
parent
32e3676b02
commit
359e739fe8
@ -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,
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
return None;
|
||||
}
|
||||
|
||||
protected highlightString(
|
||||
i: number,
|
||||
syntax: FileType,
|
||||
ch: string,
|
||||
): Option<number> {
|
||||
// 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;
|
||||
}
|
||||
if (syntax.flags.strings && ch === '"' || ch === "'") {
|
||||
while (true) {
|
||||
this.hl.push(HighlightType.String);
|
||||
i += 1;
|
||||
prevIsSeparator = true;
|
||||
continue;
|
||||
} else if (prevIsSeparator && ch === '"' || ch === "'") {
|
||||
highlighting.push(HighlightType.String);
|
||||
inString = ch;
|
||||
prevIsSeparator = true;
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
if (syntax.flags.numbers && isAsciiDigit(ch)) {
|
||||
if (i > 0 && !isSeparator(this.rchars[i - 1])) {
|
||||
return None;
|
||||
}
|
||||
|
||||
prevIsSeparator = isSeparator(ch);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
this.hl = highlighting;
|
||||
return Some(i);
|
||||
}
|
||||
|
||||
return None;
|
||||
}
|
||||
|
||||
public render(offset: number, len: number): string {
|
||||
|
Loading…
Reference in New Issue
Block a user