Highlight character type separate from string type
All checks were successful
timw4mail/scroll/pipeline/head This commit looks good

This commit is contained in:
Timothy Warren 2024-07-23 14:21:57 -04:00
parent 58490f1c51
commit c31933ed9b
5 changed files with 85 additions and 33 deletions

View File

@ -77,13 +77,9 @@ export class CFile extends AbstractFileType {
public readonly operators = [ public readonly operators = [
'>>>=', '>>>=',
'**=', '**=',
'<<=',
'>>=',
'&&=', '&&=',
'||=', '||=',
'??=', '??=',
'===',
'!==',
'>>>', '>>>',
'<=>', '<=>',
'<<=', '<<=',
@ -137,6 +133,7 @@ export class CFile extends AbstractFileType {
'%', '%',
'-', '-',
'+', '+',
'*',
'&', '&',
'|', '|',
'^', '^',

View File

@ -1,6 +1,6 @@
import { node_path as path } from '../runtime/mod.ts'; import { node_path as path } from '../runtime/mod.ts';
import { AbstractFileType } from './base.ts'; import { AbstractFileType } from './base.ts';
import { CFile } from './c.ts' import { CFile } from './c.ts';
import { CSSFile } from './css.ts'; import { CSSFile } from './css.ts';
import { JavaScriptFile, TypeScriptFile } from './javascript.ts'; import { JavaScriptFile, TypeScriptFile } from './javascript.ts';
import { ShellFile } from './shell.ts'; import { ShellFile } from './shell.ts';

View File

@ -4,6 +4,7 @@ export enum HighlightType {
None, None,
Number, Number,
Match, Match,
Character,
String, String,
SingleLineComment, SingleLineComment,
MultiLineComment, MultiLineComment,
@ -20,11 +21,14 @@ export function highlightToColor(type: HighlightType): string {
case HighlightType.Match: case HighlightType.Match:
return Ansi.color256(21); return Ansi.color256(21);
case HighlightType.Character:
return Ansi.color256(207);
case HighlightType.String: case HighlightType.String:
return Ansi.color256(45); return Ansi.color256(45);
case HighlightType.SingleLineComment: case HighlightType.SingleLineComment:
return Ansi.color256(201); return Ansi.color256(248);
case HighlightType.MultiLineComment: case HighlightType.MultiLineComment:
return Ansi.color256(240); return Ansi.color256(240);

View File

@ -88,14 +88,14 @@ export class Option<T> {
/** /**
* The wrapped value is not null or undefined * The wrapped value is not null or undefined
*/ */
isSome(): boolean { public isSome(): boolean {
return isSome(this.inner); return isSome(this.inner);
} }
/** /**
* The wrapped value is null or undefined * The wrapped value is null or undefined
*/ */
isNone(): boolean { public isNone(): boolean {
return !this.isSome(); return !this.isSome();
} }
@ -107,7 +107,7 @@ export class Option<T> {
* *
* @param fn A boolean check to run on the wrapped value * @param fn A boolean check to run on the wrapped value
*/ */
isSomeAnd(fn: (a: T) => boolean): boolean { public isSomeAnd(fn: (a: T) => boolean): boolean {
return isSome(this.inner) ? fn(this.inner.value) : false; return isSome(this.inner) ? fn(this.inner.value) : false;
} }
@ -119,7 +119,7 @@ export class Option<T> {
* *
* @param fn A function returning a boolean value * @param fn A function returning a boolean value
*/ */
isNoneAnd(fn: () => boolean): boolean { public isNoneAnd(fn: () => boolean): boolean {
return this.isNone() ? fn() : false; return this.isNone() ? fn() : false;
} }
@ -130,7 +130,7 @@ export class Option<T> {
* *
* @param fn A function that takes the inner value of the `Option` and returns a new one * @param fn A function that takes the inner value of the `Option` and returns a new one
*/ */
map<U>(fn: (a: T) => U): Option<U> { public map<U>(fn: (a: T) => U): Option<U> {
return isSome(this.inner) ? Option.from(fn(this.inner.value)) : Option.None; return isSome(this.inner) ? Option.from(fn(this.inner.value)) : Option.None;
} }
@ -142,7 +142,7 @@ export class Option<T> {
* @param def The default value to return if this `Option` is `None` * @param def The default value to return if this `Option` is `None`
* @param fn A function that takes the inner value of this `Option` and returns a new value * @param fn A function that takes the inner value of this `Option` and returns a new value
*/ */
mapOr<U>(def: U, fn: (a: T) => U): U { public mapOr<U>(def: U, fn: (a: T) => U): U {
return isSome(this.inner) ? fn(this.inner.value) : def; return isSome(this.inner) ? fn(this.inner.value) : def;
} }
@ -154,7 +154,7 @@ export class Option<T> {
* @param def A function to return a value if this `Option` is `None` * @param def A function to return a value if this `Option` is `None`
* @param fn A function that takes the inner value of this `Option` and returns a new value * @param fn A function that takes the inner value of this `Option` and returns a new value
*/ */
mapOrElse<U>(def: () => U, fn: (a: T) => U): U { public mapOrElse<U>(def: () => U, fn: (a: T) => U): U {
return isSome(this.inner) ? fn(this.inner.value) : def(); return isSome(this.inner) ? fn(this.inner.value) : def();
} }
@ -164,7 +164,7 @@ export class Option<T> {
* *
* @param err * @param err
*/ */
assert(err: string): T | never { public assert(err: string): T | never {
if (isSome(this.inner)) { if (isSome(this.inner)) {
return this.inner.value; return this.inner.value;
} }
@ -177,7 +177,7 @@ export class Option<T> {
* *
* If `None`, throws an exception. * If `None`, throws an exception.
*/ */
unwrap(): T | never { public unwrap(): T | never {
return this.assert("Called unwrap on a 'None'"); return this.assert("Called unwrap on a 'None'");
} }
@ -187,7 +187,7 @@ export class Option<T> {
* *
* @param def Value to return on `None` value * @param def Value to return on `None` value
*/ */
unwrapOr(def: T): T { public unwrapOr(def: T): T {
return isSome(this.inner) ? this.inner.value : def; return isSome(this.inner) ? this.inner.value : def;
} }
@ -197,7 +197,7 @@ export class Option<T> {
* *
* @param f Function to run on `None` value * @param f Function to run on `None` value
*/ */
unwrapOrElse(f: () => T): T { public unwrapOrElse(f: () => T): T {
return isSome(this.inner) ? this.inner.value : f(); return isSome(this.inner) ? this.inner.value : f();
} }
@ -207,7 +207,7 @@ export class Option<T> {
* *
* @param optb Another `Option` to check * @param optb Another `Option` to check
*/ */
and<U>(optb: Option<U>): Option<U> { public and<U>(optb: Option<U>): Option<U> {
return isSome(this.inner) ? optb : Option.None; return isSome(this.inner) ? optb : Option.None;
} }
@ -219,7 +219,7 @@ export class Option<T> {
* *
* @param f function to run on the wrapped value * @param f function to run on the wrapped value
*/ */
andThen<U>(f: (a: T) => Option<U>): Option<U> { public andThen<U>(f: (a: T) => Option<U>): Option<U> {
return isSome(this.inner) ? f(this.inner.value) : Option.None; return isSome(this.inner) ? f(this.inner.value) : Option.None;
} }
@ -228,7 +228,7 @@ export class Option<T> {
* *
* @param optb The `Option` to return if this `Option` is `None` * @param optb The `Option` to return if this `Option` is `None`
*/ */
or(optb: Option<T>): Option<T> { public or(optb: Option<T>): Option<T> {
return this.isNone() ? optb : this; return this.isNone() ? optb : this;
} }
@ -239,7 +239,7 @@ export class Option<T> {
* *
* @param f A function to return a different `Option` * @param f A function to return a different `Option`
*/ */
orElse(f: () => Option<T>): Option<T> { public orElse(f: () => Option<T>): Option<T> {
return this.isNone() ? f() : this; return this.isNone() ? f() : this;
} }
@ -247,7 +247,7 @@ export class Option<T> {
* Create a string representation of the `Option`, * Create a string representation of the `Option`,
* mostly for debugging * mostly for debugging
*/ */
toString(): string { public toString(): string {
const innerValue = (isSome(this.inner)) const innerValue = (isSome(this.inner))
? JSON.stringify(this.inner.value) ? JSON.stringify(this.inner.value)
: ''; : '';

View File

@ -14,6 +14,9 @@ import { highlightToColor, HighlightType } from './highlight.ts';
import Option, { None, Some } from './option.ts'; import Option, { None, Some } from './option.ts';
import { SearchDirection } from './types.ts'; import { SearchDirection } from './types.ts';
const SINGLE_QUOTE = "'";
const DOUBLE_QUOTE = '"';
/** /**
* One row of text in the current document. In order to handle * One row of text in the current document. In order to handle
* multi-byte graphemes, all operations are done on an * multi-byte graphemes, all operations are done on an
@ -321,20 +324,22 @@ export class Row {
.orElse(() => this.highlightPrimaryKeywords(i, syntax)) .orElse(() => this.highlightPrimaryKeywords(i, syntax))
.orElse(() => this.highlightSecondaryKeywords(i, syntax)) .orElse(() => this.highlightSecondaryKeywords(i, syntax))
.orElse(() => this.highlightString(i, syntax)) .orElse(() => this.highlightString(i, syntax))
.orElse(() => this.highlightCharacter(i, syntax))
.orElse(() => this.highlightNumber(i, syntax)) .orElse(() => this.highlightNumber(i, syntax))
.orElse(() => this.highlightOperators(i, syntax)); .orElse(() => this.highlightOperators(i, syntax));
if (maybeNext.isSome()) { if (maybeNext.isNone()) {
this.hl.push(HighlightType.None);
i += 1;
continue;
}
const next = maybeNext.unwrap(); const next = maybeNext.unwrap();
if (next >= this.rsize) { if (next >= this.rsize) {
break; break;
} }
i = next; i = next;
continue;
}
this.hl.push(HighlightType.None);
i += 1;
} }
this.highlightMatch(word); this.highlightMatch(word);
@ -391,8 +396,9 @@ export class Row {
// Highlight single-line comments // Highlight single-line comments
if (syntax.singleLineComment.isSome()) { if (syntax.singleLineComment.isSome()) {
const commentStart = syntax.singleLineComment.unwrap(); const commentStart = syntax.singleLineComment.unwrap();
const hasCommentStart = this.rIndexOf(commentStart).isSome();
if ( if (
this.toString().indexOf(commentStart) === this.charIndexToByteIndex(i) hasCommentStart && this.rIndexOf(commentStart).unwrap() === i
) { ) {
for (; i < this.rsize; i++) { for (; i < this.rsize; i++) {
this.hl.push(HighlightType.SingleLineComment); this.hl.push(HighlightType.SingleLineComment);
@ -516,13 +522,58 @@ export class Row {
return None; return None;
} }
protected highlightCharacter(
i: number,
syntax: FileType,
): Option<number> {
if (!syntax.flags.characters) {
return None;
}
// Highlight character literals
const ch = this.rchars[i];
if (ch === SINGLE_QUOTE) {
while (true) {
this.hl.push(HighlightType.Character);
i += 1;
if (i === this.rsize) {
break;
}
const nextChar = this.rchars[i];
// Make sure to continue highlighting if
// you have an escaped character delimeter
if (nextChar === '\\') {
this.hl.push(HighlightType.Character);
i += 1;
continue;
}
if (nextChar === ch) {
break;
}
}
this.hl.push(HighlightType.Character);
i += 1;
return Some(i);
}
return None;
}
protected highlightString( protected highlightString(
i: number, i: number,
syntax: FileType, syntax: FileType,
): Option<number> { ): Option<number> {
if (!syntax.flags.strings) {
return None;
}
// Highlight strings // Highlight strings
const ch = this.rchars[i]; const ch = this.rchars[i];
if (syntax.flags.strings && ch === '"' || ch === "'") { if (
ch === DOUBLE_QUOTE ||
((!syntax.flags.characters) && ch === SINGLE_QUOTE)
) {
while (true) { while (true) {
this.hl.push(HighlightType.String); this.hl.push(HighlightType.String);
i += 1; i += 1;