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 = [
'>>>=',
'**=',
'<<=',
'>>=',
'&&=',
'||=',
'??=',
'===',
'!==',
'>>>',
'<=>',
'<<=',
@ -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 { AbstractFileType } from './base.ts';
import { CFile } from './c.ts'
import { CFile } from './c.ts';
import { CSSFile } from './css.ts';
import { JavaScriptFile, TypeScriptFile } from './javascript.ts';
import { ShellFile } from './shell.ts';

View File

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

View File

@ -88,14 +88,14 @@ export class Option<T> {
/**
* The wrapped value is not null or undefined
*/
isSome(): boolean {
public isSome(): boolean {
return isSome(this.inner);
}
/**
* The wrapped value is null or undefined
*/
isNone(): boolean {
public isNone(): boolean {
return !this.isSome();
}
@ -107,7 +107,7 @@ export class Option<T> {
*
* @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;
}
@ -119,7 +119,7 @@ export class Option<T> {
*
* @param fn A function returning a boolean value
*/
isNoneAnd(fn: () => boolean): boolean {
public isNoneAnd(fn: () => boolean): boolean {
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
*/
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;
}
@ -142,7 +142,7 @@ export class Option<T> {
* @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
*/
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;
}
@ -154,7 +154,7 @@ export class Option<T> {
* @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
*/
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();
}
@ -164,7 +164,7 @@ export class Option<T> {
*
* @param err
*/
assert(err: string): T | never {
public assert(err: string): T | never {
if (isSome(this.inner)) {
return this.inner.value;
}
@ -177,7 +177,7 @@ export class Option<T> {
*
* If `None`, throws an exception.
*/
unwrap(): T | never {
public unwrap(): T | never {
return this.assert("Called unwrap on a 'None'");
}
@ -187,7 +187,7 @@ export class Option<T> {
*
* @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;
}
@ -197,7 +197,7 @@ export class Option<T> {
*
* @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();
}
@ -207,7 +207,7 @@ export class Option<T> {
*
* @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;
}
@ -219,7 +219,7 @@ export class Option<T> {
*
* @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;
}
@ -228,7 +228,7 @@ export class Option<T> {
*
* @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;
}
@ -239,7 +239,7 @@ export class Option<T> {
*
* @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;
}
@ -247,7 +247,7 @@ export class Option<T> {
* Create a string representation of the `Option`,
* mostly for debugging
*/
toString(): string {
public toString(): string {
const innerValue = (isSome(this.inner))
? 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 { SearchDirection } from './types.ts';
const SINGLE_QUOTE = "'";
const DOUBLE_QUOTE = '"';
/**
* One row of text in the current document. In order to handle
* multi-byte graphemes, all operations are done on an
@ -321,20 +324,22 @@ export class Row {
.orElse(() => this.highlightPrimaryKeywords(i, syntax))
.orElse(() => this.highlightSecondaryKeywords(i, syntax))
.orElse(() => this.highlightString(i, syntax))
.orElse(() => this.highlightCharacter(i, syntax))
.orElse(() => this.highlightNumber(i, syntax))
.orElse(() => this.highlightOperators(i, syntax));
if (maybeNext.isSome()) {
const next = maybeNext.unwrap();
if (next >= this.rsize) {
break;
}
i = next;
if (maybeNext.isNone()) {
this.hl.push(HighlightType.None);
i += 1;
continue;
}
this.hl.push(HighlightType.None);
i += 1;
const next = maybeNext.unwrap();
if (next >= this.rsize) {
break;
}
i = next;
}
this.highlightMatch(word);
@ -391,8 +396,9 @@ export class Row {
// Highlight single-line comments
if (syntax.singleLineComment.isSome()) {
const commentStart = syntax.singleLineComment.unwrap();
const hasCommentStart = this.rIndexOf(commentStart).isSome();
if (
this.toString().indexOf(commentStart) === this.charIndexToByteIndex(i)
hasCommentStart && this.rIndexOf(commentStart).unwrap() === i
) {
for (; i < this.rsize; i++) {
this.hl.push(HighlightType.SingleLineComment);
@ -516,13 +522,58 @@ export class Row {
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(
i: number,
syntax: FileType,
): Option<number> {
if (!syntax.flags.strings) {
return None;
}
// Highlight strings
const ch = this.rchars[i];
if (syntax.flags.strings && ch === '"' || ch === "'") {
if (
ch === DOUBLE_QUOTE ||
((!syntax.flags.characters) && ch === SINGLE_QUOTE)
) {
while (true) {
this.hl.push(HighlightType.String);
i += 1;