<commit_before>/*
Copyright 2019 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import {MatrixClient} from "matrix-js-sdk/src/client";
import {RoomMember} from "matrix-js-sdk/src/models/room-member";
import {Room} from "matrix-js-sdk/src/models/room";

import AutocompleteWrapperModel, {
    GetAutocompleterComponent,
    UpdateCallback,
    UpdateQuery,
} from "./autocomplete";
import * as Avatar from "../Avatar";

interface ISerializedPart {
    type: Type.Plain | Type.Newline | Type.Command | Type.PillCandidate;
    text: string;
}

interface ISerializedPillPart {
    type: Type.AtRoomPill | Type.RoomPill | Type.UserPill;
    text: string;
    resourceId: string;
}

export type SerializedPart = ISerializedPart | ISerializedPillPart;

enum Type {
    Plain = "plain",
    Newline = "newline",
    Command = "command",
    UserPill = "user-pill",
    RoomPill = "room-pill",
    AtRoomPill = "at-room-pill",
    PillCandidate = "pill-candidate",
}

interface IBasePart {
    text: string;
    type: Type.Plain | Type.Newline;
    canEdit: boolean;

    createAutoComplete(updateCallback: UpdateCallback): void;

    serialize(): SerializedPart;
    remove(offset: number, len: number): string;
    split(offset: number): IBasePart;
    validateAndInsert(offset: number, str: string, inputType: string): boolean;
    appendUntilRejected(str: string, inputType: string): string;
    updateDOMNode(node: Node);
    canUpdateDOMNode(node: Node);
    toDOMNode(): Node;
}

interface IPillCandidatePart extends Omit<IBasePart, "type" | "createAutoComplete"> {
    type: Type.PillCandidate | Type.Command;
    createAutoComplete(updateCallback: UpdateCallback): AutocompleteWrapperModel;
}

interface IPillPart extends Omit<IBasePart, "type" | "resourceId"> {
    type: Type.AtRoomPill | Type.RoomPill | Type.UserPill;
    resourceId: string;
}

export type Part = IBasePart | IPillCandidatePart | IPillPart;

abstract class BasePart {
    protected _text: string;

    constructor(text = "") {
        this._text = text;
    }

    acceptsInsertion(chr: string, offset: number, inputType: string) {
        return true;
    }

    acceptsRemoval(position: number, chr: string) {
        return true;
    }

    merge(part: Part) {
        return false;
    }

    split(offset: number) {
        const splitText = this.text.substr(offset);
        this._text = this.text.substr(0, offset);
        return new PlainPart(splitText);
    }

    // removes len chars, or returns the plain text this part should be replaced with
    // if the part would become invalid if it removed everything.
    remove(offset: number, len: number) {
        // validate
        const strWithRemoval = this.text.substr(0, offset) + this.text.substr(offset + len);
        for (let i = offset; i < (len + offset); ++i) {
            const chr = this.text.charAt(i);
            if (!this.acceptsRemoval(i, chr)) {
                return strWithRemoval;
            }
        }
        this._text = strWithRemoval;
    }

    // append str, returns the remaining string if a character was rejected.
    appendUntilRejected(str: string, inputType: string) {
        const offset = this.text.length;
        for (let i = 0; i < str.length; ++i) {
            const chr = str.charAt(i);
            if (!this.acceptsInsertion(chr, offset + i, inputType)) {
                this._text = this._text + str.substr(0, i);
                return str.substr(i);
            }
        }
        this._text = this._text + str;
    }

    // inserts str at offset if all the characters in str were accepted, otherwise don't do anything
    // return whether the str was accepted or not.
    validateAndInsert(offset: number, str: string, inputType: string) {
        for (let i = 0; i < str.length; ++i) {
            const chr = str.charAt(i);
            if (!this.acceptsInsertion(chr, offset + i, inputType)) {
                return false;
            }
        }
        const beforeInsert = this._text.substr(0, offset);
        const afterInsert = this._text.substr(offset);
        this._text = beforeInsert + str + afterInsert;
        return true;
    }

    createAutoComplete(updateCallback: UpdateCallback): void {}

    trim(len: number) {
        const remaining = this._text.substr(len);
        this._text = this._text.substr(0, len);
        return remaining;
    }

    get text() {
        return this._text;
    }

    abstract get type(): Type;

    get canEdit() {
        return true;
    }

    toString() {
        return `${this.type}(${this.text})`;
    }

    serialize(): SerializedPart {
        return {
            type: this.type as ISerializedPart["type"],
            text: this.text,
        };
    }

    abstract updateDOMNode(node: Node);
    abstract canUpdateDOMNode(node: Node);
    abstract toDOMNode(): Node;
}

abstract class PlainBasePart extends BasePart {
    acceptsInsertion(chr: string, offset: number, inputType: string) {
        if (chr === "\n") {
            return false;
        }
        // when not pasting or dropping text, reject characters that should start a pill candidate
        if (inputType !== "insertFromPaste" && inputType !== "insertFromDrop") {
            return chr !== "@" && chr !== "#" && chr !== ":";
        }
        return true;
    }

    toDOMNode() {
        return document.createTextNode(this.text);
    }

    merge(part) {
        if (part.type === this.type) {<commit_msg>Update PlainBasePart to only split on space boundaries<commit_after>/*
Copyright 2019 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import {MatrixClient} from "matrix-js-sdk/src/client";
import {RoomMember} from "matrix-js-sdk/src/models/room-member";
import {Room} from "matrix-js-sdk/src/models/room";

import AutocompleteWrapperModel, {
    GetAutocompleterComponent,
    UpdateCallback,
    UpdateQuery,
} from "./autocomplete";
import * as Avatar from "../Avatar";

interface ISerializedPart {
    type: Type.Plain | Type.Newline | Type.Command | Type.PillCandidate;
    text: string;
}

interface ISerializedPillPart {
    type: Type.AtRoomPill | Type.RoomPill | Type.UserPill;
    text: string;
    resourceId: string;
}

export type SerializedPart = ISerializedPart | ISerializedPillPart;

enum Type {
    Plain = "plain",
    Newline = "newline",
    Command = "command",
    UserPill = "user-pill",
    RoomPill = "room-pill",
    AtRoomPill = "at-room-pill",
    PillCandidate = "pill-candidate",
}

interface IBasePart {
    text: string;
    type: Type.Plain | Type.Newline;
    canEdit: boolean;

    createAutoComplete(updateCallback: UpdateCallback): void;

    serialize(): SerializedPart;
    remove(offset: number, len: number): string;
    split(offset: number): IBasePart;
    validateAndInsert(offset: number, str: string, inputType: string): boolean;
    appendUntilRejected(str: string, inputType: string): string;
    updateDOMNode(node: Node);
    canUpdateDOMNode(node: Node);
    toDOMNode(): Node;
}

interface IPillCandidatePart extends Omit<IBasePart, "type" | "createAutoComplete"> {
    type: Type.PillCandidate | Type.Command;
    createAutoComplete(updateCallback: UpdateCallback): AutocompleteWrapperModel;
}

interface IPillPart extends Omit<IBasePart, "type" | "resourceId"> {
    type: Type.AtRoomPill | Type.RoomPill | Type.UserPill;
    resourceId: string;
}

export type Part = IBasePart | IPillCandidatePart | IPillPart;

abstract class BasePart {
    protected _text: string;

    constructor(text = "") {
        this._text = text;
    }

    acceptsInsertion(chr: string, offset: number, inputType: string) {
        return true;
    }

    acceptsRemoval(position: number, chr: string) {
        return true;
    }

    merge(part: Part) {
        return false;
    }

    split(offset: number) {
        const splitText = this.text.substr(offset);
        this._text = this.text.substr(0, offset);
        return new PlainPart(splitText);
    }

    // removes len chars, or returns the plain text this part should be replaced with
    // if the part would become invalid if it removed everything.
    remove(offset: number, len: number) {
        // validate
        const strWithRemoval = this.text.substr(0, offset) + this.text.substr(offset + len);
        for (let i = offset; i < (len + offset); ++i) {
            const chr = this.text.charAt(i);
            if (!this.acceptsRemoval(i, chr)) {
                return strWithRemoval;
            }
        }
        this._text = strWithRemoval;
    }

    // append str, returns the remaining string if a character was rejected.
    appendUntilRejected(str: string, inputType: string) {
        const offset = this.text.length;
        for (let i = 0; i < str.length; ++i) {
            const chr = str.charAt(i);
            if (!this.acceptsInsertion(chr, offset + i, inputType)) {
                this._text = this._text + str.substr(0, i);
                return str.substr(i);
            }
        }
        this._text = this._text + str;
    }

    // inserts str at offset if all the characters in str were accepted, otherwise don't do anything
    // return whether the str was accepted or not.
    validateAndInsert(offset: number, str: string, inputType: string) {
        for (let i = 0; i < str.length; ++i) {
            const chr = str.charAt(i);
            if (!this.acceptsInsertion(chr, offset + i, inputType)) {
                return false;
            }
        }
        const beforeInsert = this._text.substr(0, offset);
        const afterInsert = this._text.substr(offset);
        this._text = beforeInsert + str + afterInsert;
        return true;
    }

    createAutoComplete(updateCallback: UpdateCallback): void {}

    trim(len: number) {
        const remaining = this._text.substr(len);
        this._text = this._text.substr(0, len);
        return remaining;
    }

    get text() {
        return this._text;
    }

    abstract get type(): Type;

    get canEdit() {
        return true;
    }

    toString() {
        return `${this.type}(${this.text})`;
    }

    serialize(): SerializedPart {
        return {
            type: this.type as ISerializedPart["type"],
            text: this.text,
        };
    }

    abstract updateDOMNode(node: Node);
    abstract canUpdateDOMNode(node: Node);
    abstract toDOMNode(): Node;
}

abstract class PlainBasePart extends BasePart {
    acceptsInsertion(chr: string, offset: number, inputType: string) {
        if (chr === "\n") {
            return false;
        }
        // when not pasting or dropping text, reject characters that should start a pill candidate
        if (inputType !== "insertFromPaste" && inputType !== "insertFromDrop") {
            if (chr !== "@" && chr !== "#" && chr !== ":") {
                return true;
            }
            // only split if the previous character is a space
            return this._text[offset - 1] !== " ";
        }
        return true;
    }

    toDOMNode() {
        return document.createTextNode(this.text);
    }

    merge(part) {
        if (part.type === this.type) {
{'fraction_non_alphanumeric': 0.11071810479559102, 'fraction_numerical': 0.0032080282964547175, 'mean_word_length': 2.6281706953148314, 'pattern_counts': {'":': 2, '<': 13, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7206635', 'n_tokens_mistral': 3595, 'n_tokens_neox': 3491, 'n_words': 1043}