/*
    This file is part of Msc-generator.
    Copyright (C) 2008-2024 Zoltan Turanyi
    Distributed under GNU Affero General Public License.

    Msc-generator is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Msc-generator is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with Msc-generator.  If not, see <http://www.gnu.org/licenses/>.
*/

/** @file csh.h The declaration of classes for Color Syntax Highlighting.
 * @ingroup libcgencommon_files  */

#ifndef CSH_H
#define CSH_H

#include <vector>
#include <list>
#include <string>
#include <set>
#include <map>
#include <algorithm>
#include <memory>
#include <array>
#include "cgen_color.h"
#include "cgen_context.h"
#include "utf8utils.h"
#include <limits>

 /** An (open) range inside an input file. */
struct CshPos
{
    CharByteIndex first_pos; ///<The first character of the range.
    CharByteIndex end_pos;   ///<The character beyond the last character of the range.
    [[nodiscard]] constexpr int char_length() const noexcept { return end_pos.char_index - first_pos.char_index; }
    [[nodiscard]] constexpr int byte_length() const noexcept { return end_pos.byte_index - first_pos.byte_index; }
    [[nodiscard]] constexpr bool empty() const noexcept { _ASSERT(IsValid()); return char_length() <= 0; }
    [[nodiscard]] constexpr CshPos operator + (const CshPos &a) const noexcept { return CshPos(std::min(first_pos, a.first_pos), std::max(end_pos, a.end_pos)); }
    constexpr CshPos &operator += (const CshPos &a) noexcept { first_pos = std::min(first_pos, a.first_pos); end_pos = std::max(end_pos, a.end_pos); return *this; }
    [[nodiscard]] constexpr auto operator <=>(const CshPos&) const noexcept = default;
    [[nodiscard]] constexpr bool IsWithinChar(int char_index) const noexcept { return first_pos.char_index <= char_index && char_index < end_pos.char_index; }
    [[nodiscard]] constexpr bool IsWithin(CharByteIndex p) const noexcept { return first_pos<=p && p<end_pos; } ///<Returns true if positin p is inside the range.
    [[nodiscard]] constexpr bool IsWithin(const CshPos &p) const noexcept {return first_pos<=p.first_pos && p.end_pos<=end_pos;} ///<Returns true if another range is completely within the range.
    [[nodiscard]] constexpr bool Overlaps(const CshPos &p) const noexcept { return p.first_pos<end_pos && first_pos<p.end_pos; }
    template<class PosList> static constexpr void AdjustList(PosList &list, CharByteIndex start_char, CharByteIndex offset);
    [[nodiscard]] constexpr static CshPos invalid() noexcept { return CshPos{ .first_pos = CharByteIndex::invalid(), .end_pos = CharByteIndex::invalid() }; }
    [[nodiscard]] constexpr CshPos operator+(CharByteIndex delta) const noexcept { return { first_pos + delta, end_pos + delta }; }
    constexpr CshPos& operator+=(CharByteIndex delta) noexcept { first_pos += delta; end_pos += delta; return *this; }
    [[nodiscard]] constexpr bool IsValid() const noexcept { return first_pos.IsValid() && end_pos.IsValid() && first_pos<=end_pos; _ASSERT(sign(char_length()) == sign(byte_length())); }
    /** Cuts away a part of the range. No changes if not overlapping. If 'o' cuts us in two, use 'keep_front' to select which is kept.*/
    [[nodiscard]] constexpr CshPos Substract(const CshPos& o, bool keep_front = true) const noexcept {
        if (o.first_pos <= first_pos) {
            if (end_pos <= o.end_pos) return {first_pos, first_pos}; //nothing left
            return { std::max(first_pos, o.end_pos), end_pos };
        } else if (end_pos <= o.end_pos) {
            return { first_pos, std::min(end_pos, o.first_pos) };
        } else {
            if (keep_front) return { first_pos, o.first_pos };
            return { o.end_pos, end_pos };
        }
    }
};

/** Describes types of language elements to color differently.*/
enum EColorSyntaxType : uint8_t {
    COLOR_NORMAL = 0,      ///<Regular text
    COLOR_KEYWORD,         ///<A keyword, like commands or 'parallel'
    COLOR_KEYWORD_PARTIAL, ///<A partially typed keyword
    COLOR_KEYWORD_MSCGEN,  ///<An msc-gen only keyword
    COLOR_EQUAL,           ///<The equal sign
    COLOR_SEMICOLON,       ///<The semicolon sign
    COLOR_COLON,           ///<The colon character
    COLOR_COMMA,           ///<The comma character
    COLOR_SYMBOL,          ///<A symbol, e.g., arrows, box symbols, etc.
    COLOR_SYMBOL_MSCGEN,   ///<A symbol, e.g., arrows, box symbols, etc.
    COLOR_BRACE,           ///<An opening or closing brace {}
    COLOR_BRACKET,         ///<An opening or closing bracket []
    COLOR_PARENTHESIS,     ///<An opening or closing parenthesis ()
    COLOR_DESIGNNAME,      ///<The name of a design
    COLOR_STYLENAME,       ///<The name of a style
    COLOR_ENTITYNAME,      ///<The name of an entity (we also have partial, see below)
    COLOR_ENTITYNAME_FIRST,///<The name of an entity when first occurs in the file
    COLOR_COLORNAME,       ///<The name of a color
    COLOR_COLORDEF,        ///<The description of a color other than a name
    COLOR_MARKERNAME,      ///<The name of a marker
    COLOR_MARKERNAME_PARTIAL,///<The name of a marker partially written
    COLOR_PROCNAME,        ///<The name of a procedure
    COLOR_INCLUDEFILE,     ///<The name of a file to include
    COLOR_PARAMNAME,       ///<The name of a procedure parameter ($xxx)
    COLOR_OPTIONNAME,      ///<The name of a chart option
    COLOR_OPTIONNAME_PARTIAL,///<The name of a chart option partially written
    COLOR_OPTIONNAME_MSCGEN,///<The name of an mscgen-only chart option
    COLOR_ATTRNAME,        ///<The name of an attribute
    COLOR_ATTRNAME_PARTIAL,///<The name of a attribute partially written
    COLOR_ATTRNAME_MSCGEN, ///<The name of an mscgen-only attribute
    COLOR_ATTRVALUE,       ///<The value of an attribute
    COLOR_ATTRVALUE_EMPH,  ///<Emphasized part of the attribute value
    COLOR_LABEL_TEXT,      ///<The text of a label, either after a colon or between quotation marks
    COLOR_LABEL_ESCAPE,    ///<The text of a label escape, such as '\n'
    COLOR_ERROR,           ///<A place where an error was detected.
    COLOR_NO_ERROR,        ///<The inverse of error formatting
    COLOR_COMMENT,         ///<A comment
    COLOR_ENTITYNAME_PARTIAL,///<The name of an entity partially written
    COLOR_MAX              ///<Maximum value. Note: The Windows GUI also uses this value to mark places (in CshErrorList) that are no longer in error and shall be re-colored.
};

/** Describes a colored range in the input file.*/
struct CshEntry : public CshPos
{
    EColorSyntaxType color; ///<The type of the language element in the range
    constexpr CshEntry() noexcept = default;
    constexpr CshEntry(const CshEntry&) noexcept = default;
    constexpr CshEntry(const CshPos &a, EColorSyntaxType c) noexcept : CshPos(a), color(c) {}
    constexpr CshEntry(CharByteIndex f, CharByteIndex l, EColorSyntaxType c) noexcept : CshPos{ f, l }, color(c) {}
    constexpr auto operator<=>(const CshEntry& o) const noexcept = default;
};


/** An ordered collection of position ranges.*/
using CshPosList = std::vector<CshPos>;

/** An ordered collection of colored ranges.*/
class CshListType : public std::vector<CshEntry>
{
public:
    bool CheckIfOverlap(const CshPos &e) const;
    void AddToBack(const CshEntry &e) { _ASSERT(!CheckIfOverlap(e));  push_back(e); }        ///<Add to the end of the collection
    template <typename PosList1, typename PosList2>
    void DiffInto(const PosList1 &old_list, const PosList2 &new_list, EColorSyntaxType neutral);
    void SortByPos() { std::ranges::sort(*this); }
};

/** An error detected during csh parse */
struct CshError : public CshEntry
{
    std::string text; ///<The text of the error message.
    CshError() noexcept = default;
    CshError(const CshError&) = default;
    CshError(CshError&&) = default;
    CshError(const CshEntry &e, std::string_view t) : CshEntry(e), text(t) {}
    CshError(const CshPos &a, EColorSyntaxType c, std::string_view t) : CshEntry(a, c), text(t) {}
    CshError(CharByteIndex f, CharByteIndex l, EColorSyntaxType c, std::string_view t) : CshEntry(f, l, c), text(t) {}
    CshError& operator=(const CshError&) = default;
    CshError& operator=(CshError&&) = default;
};


/** A list of errors detected during csh parse */
struct CshErrorList {
    std::vector<CshError> error_ranges;  ///<The error messages & character ranges. Always sorted, contains only COLOR_ERROR or COLOR_MAX types. The latter is used to mark places that are no longer in error and shall be re-colored.
    bool Add(CshPos pos, std::string &&t); ///<Add an error to the collection. Returns true if newly added.
    bool CheckIfErrorOrMaxColorsOnly() const;
    void clear() {error_ranges.clear(); }
    void swap(CshErrorList &a) { error_ranges.swap(a.error_ranges); }
};

struct MultiEntry : CshPos {
    bool is_param = false;
    std::string_view content;
    MultiEntry(const CshPos& pos, std::string_view content, bool p = false) noexcept : CshPos(pos), is_param(p), content(content) {}
};

using MultiList = std::vector<MultiEntry>;

/** Checks if all elements are sorted and non-overlapping.
 *  Used in debug mode only.*/
template <typename PosList>
bool CheckOrderedAndNonOverlapping(const PosList &l) {
    for (unsigned u = 1; u<l.size(); u++)
        if (l.at(u-1).end_pos > l.at(u).first_pos)
            return false;
    return true;
}

/** Flags to describe appearance of colored text*/
enum EColorSyntaxFlag {
    COLOR_FLAG_BOLD = 1,                 ///<Text shall be bold
    COLOR_FLAG_ITALICS = 2,              ///<Text shall be italics
    COLOR_FLAG_UNDERLINE = 4,            ///<Text shall be underlined
    COLOR_FLAG_COLOR = 8,                ///<Text shall be of specific color (actual value in separate members)
    COLOR_FLAG_DIFFERENT_NO_DRAW = 16,   ///<This Csh entry shall be recorded, even if text appears exactly as COLOR_NORMAL, but no need to draw (used by smart ident)
    COLOR_FLAG_DIFFERENT_DRAW = 32       ///<This Csh entry shall be both recorded and drawn even if appears exactly as COLOR_NORMAL (e.g., comments inside a label)
};

/** Describes the appearance of colored text*/
struct ColorSyntaxAppearance {
    unsigned effects;    ///<Value of flags
    unsigned mask;       ///<Mask for the flags. Needed to be able to combine.
    unsigned char r;     ///<Red value
    unsigned char g;     ///<Green value
    unsigned char b;     ///<Blue value
    void SetColor(unsigned char rr, unsigned char gg, unsigned char bb)
    { r = rr; g = gg; b = bb; effects |= COLOR_FLAG_COLOR; }
    bool operator==(const struct ColorSyntaxAppearance &p) const;
    std::string Print() const;
};

/** How many coloring schemes shall we have.*/
#define CSH_SCHEME_MAX 4

/** Definition of the appearance of language elements for each coloring scheme.*/
extern ColorSyntaxAppearance MscCshAppearanceList[CSH_SCHEME_MAX][COLOR_MAX];
/** Initializes the coloring schemes*/
void MscInitializeCshAppearanceList(void);


class Canvas;
class StringFormat;
/** The X size of small symbols in the hint popup.*/
#define HINT_GRAPHIC_SIZE_X 25
/** The Y size of small symbols in the hint popup.*/
#define HINT_GRAPHIC_SIZE_Y 18
/** The parameter for hint drawing callbacks. */
using CshHintGraphicParam = size_t;
class CshHintStore;
/** A callback to draw the small symbols in hint popups.*/
typedef bool (*CshHintGraphicCallback)(Canvas*, CshHintGraphicParam, CshHintStore &);

/** A set containing style names*/
using StyleNameSet = std::set<std::string, std::less<>>;

class Context;

/** The current context during a csh parse.*/
class CshContext  : public ContextParams
{
public:
    unsigned     if_condition;///<If we are after an 'if' clause, what was the result of the condition: 0=false, 1=true, 2=error (already reported, none of the branches will fire)
    ColorSet     Colors;      ///<The colors defined so far
    StyleNameSet StyleNames;  ///<The names of styles defined so far
    CshContext(bool f, EContextParse p) : ContextParams(f, p), if_condition(2) {}
    /** Set the context to a given design (styles and colors)*/
    void SetToDesign(const Context&);
    /** Combine two contexts */
    CshContext &operator+=(const CshContext &o) {
        Colors.insert(o.Colors.begin(), o.Colors.end());
        StyleNames.insert(o.StyleNames.begin(), o.StyleNames.end());
        is_full |= o.is_full;
        return *this;
    }
};

/** The status of collecting hints*/
enum EHintStatus {
    HINT_NONE,     ///<No hint identified yet
    HINT_LOCATED,  ///<We have located the type (e.g., attribute name or keyword) and location, but hints need filling
    HINT_FILLING,  ///<We have located the type and location and are in the process of adding hints
    HINT_READY     ///<We have a complete set of hints ready (may be empty)
};

/** The type of a hint we give.*/
enum class EHintType {
    OTHER,           ///<Anything not specifically something below
    ENTITY,          ///<An entity
    KEYWORD,         ///<Commands and other keywords
    ATTR_NAME,       ///<Attribute names (which of these shall be determined)
    ATTR_VALUE,      ///<Attribute values
    MARKER,          ///<Marker names
    ESCAPE,          ///<A string escape sequence (not including parameters)
    FILES            ///<A list of files (param to the include command)
};

/** True for those hint types that compress along the dot in the filename.
 * Well, not useful for graphviz, where attribute names include no dots.*/
constexpr bool WantsDotCompress(EHintType t)
{ return t == EHintType::ENTITY || t==EHintType::ATTR_NAME || t==EHintType::ATTR_VALUE || t==EHintType::MARKER; }

/** The situation the cursor is in when we provide hints. These can be set from preferences.*/
enum class EHintSourceType
{
    ENTITY,         ///<Entities
    KEYWORD,        ///<Mid-command keywords
    ATTR_NAME,      ///<Attribute names (which of these shall be determined)
    ATTR_VALUE,     ///<Attribute values
    MARKER,         ///<Marker names
    LINE_START,     ///<Anything that can be at the beginning of a line (keyword, option, entity)
    ESCAPE          ///<A string escape sequence (including parameters)
};

/** Describes what kind of hints we shall give to the user from a text escape.*/
enum EEscapeHintType
{
    HINTE_NONE = 0,      ///<The cursor is not at a hint position.
    HINTE_ESCAPE,        ///<The cursor is around an escape
    HINTE_PARAM_COLOR,   ///<The cursor is inside the parenthesis of a \\c() or \\C() escape
    HINTE_PARAM_STYLE,   ///<The cursor is inside the parenthesis of a \\s() escape
    HINTE_PARAM_FONT,    ///<The cursor is inside the parenthesis of a \\f() escape
    HINTE_PARAM_REF,     ///<The cursor is inside the parenthesis of a \\r() escape
    HINTE_PARAM_NUMBER,  ///<The cursor is inside the parenthesis of a \\mX() escape
    HINTE_PARAM_LINK,    ///<The cursor is inside the parenthesis of a \\L() escape
    HINTE_PARAM_SHAPE,   ///<The cursor is inside the parenthesis of a \\S() escape in the shape name section
    HINTE_PARAM_PARAM,   ///<The cursor is inside the parenthesis of a \\Q() escape
};

/** Merge two values. Error if neither are NONE */
inline EEscapeHintType operator |= (EEscapeHintType &a, EEscapeHintType b)
     { if (a==HINTE_NONE) return a = b; _ASSERT(b==HINTE_NONE); return a; }



/** The relative position of the cursor to a range in the input file.
 *
 * Note that the cursor is always between two character positions.*/
enum ECursorRelPosType {
    CURSOR_BEFORE=-2,      ///<The cursor is well before the range
    CURSOR_AFTER=-1,       ///<The cursor is well after the range
    CURSOR_AT_BEGINNING=1, ///<The cursor is immediately before the beginning of the range
    CURSOR_IN=2,           ///<The cursor is inside the range
    CURSOR_AT_END=3        ///<The cursor is right after the last character of the range
};

/** Describes the selection status of a hint in the popup window.*/
enum EHintItemSelectionState {
    HINT_ITEM_NOT_SELECTED,  ///<Not selected
    HINT_ITEM_SELECTED_HALF, ///<Selected, but the hint is not one that can be inserted into the text
    HINT_ITEM_SELECTED       ///<Selected and can be inserted into the chart text
};

/** One possibility for autocompletion valid at the cursor */
struct CshHint {
    std::array<int, 4> sort;        ///<Governs ordering of entries. Used for HINT_ESCAPEs, which do not sort alphabetically or at FUZZY matching: match length, then match pos, hint length, then user pref
    std::string decorated;          ///<Full text of the hint with formatting escapes
    EHintType  type;                ///<The type of the hint.
    bool selectable;                ///<True if this hint can be inserted to the chart text, false if it is just an explanation
    mutable std::string description;///<Descriptive text of the hint in UTF-8
    CshHintGraphicCallback callback;///<A procedure that draws the small symbol before the hint text
    CshHintGraphicParam    param;   ///<A parameter to pass to the callback
    /** @name Derived values filled in by Csh::ProcessHints
     * @{  */
    mutable std::string plain;  ///<Plain text of the hint for sorting
    mutable std::string replaceto;///<When selected, insert this to the chart text. When empty, use 'plain'.
    mutable bool keep = false;  ///<whether after insertion we shall keep the hint box up
    mutable int x_size = 0;     ///<calculated size of hint text in the hint listbox. NOT scaled for high DPI screens.
    mutable int y_size = 0;     ///<calculated size of hint text in the hint listbox. NOT scaled for high DPI screens.
    mutable std::pair<uint16_t, uint16_t> highlight = {0,0}; ///<The character indexes (begin,end) in 'plain' to highlight. Used with fuzzy or starts_with search.
    mutable bool can_autoselect = true; ///<When we do not erase bad hints, we set this to false. As the user types, we will avoid selecting any having this set to false
    /** @} */
    /** @name Derived values used by the Windows MFC GUI. They should not have been here
     *  but easier this way. TODO: refactor these to an optional.
     * @{  */
    mutable int ul_x = 0;       ///<Size of the rectangle shown in list box
    mutable int ul_y = 0;       ///<Size of the rectangle shown in list box
    mutable int br_y = 0;       ///<Size of the rectangle shown in list box
    mutable int br_x = 0;       ///<Size of the rectangle shown in list box
    mutable EHintItemSelectionState state = HINT_ITEM_NOT_SELECTED; ///<Will show if this hint is selected or not
    /** @} */
    CshHint(std::string &&d, const char *desc, EHintType t, bool s = true, CshHintGraphicCallback c = nullptr, CshHintGraphicParam p = 0, int so = 0) :
        sort{0,0,0,so}, decorated(std::move(d)), type(t), selectable(s), description(desc ? desc : ""), callback(c), param(p), keep(false) {}
    CshHint(std::string &&d, std::string &&desc, EHintType t, bool s = true, CshHintGraphicCallback c = nullptr, CshHintGraphicParam p = 0, int so = 0) :
        sort{0,0,0,so}, decorated(std::move(d)), type(t), selectable(s), description(desc), callback(c), param(p), keep(false) {}
    bool operator ==(const CshHint &o) const {return type == o.type && sort == o.sort && decorated == o.decorated;}
    std::string &GetReplacementString() { return replaceto.size() ? replaceto : plain; }
    const std::string &GetReplacementString() const { return replaceto.size() ? replaceto : plain; }
};

/** The max length of the keywords, attribute and option names.*/
#define ENUM_STRING_LEN 35

bool CshHintGraphicCallbackForAttributeNames(Canvas *canvas, CshHintGraphicParam /*p*/, CshHintStore &);
bool CshHintGraphicCallbackForColors(Canvas *canvas, CshHintGraphicParam p, CshHintStore &);
bool CshHintGraphicCallbackForDesigns(Canvas *canvas, CshHintGraphicParam /*p*/, CshHintStore &);
bool CshHintGraphicCallbackForStyles(Canvas *canvas, CshHintGraphicParam, CshHintStore &);
bool CshHintGraphicCallbackForStyles2(Canvas *canvas, CshHintGraphicParam, CshHintStore &);
bool CshHintGraphicCallbackForKeywords(Canvas *canvas, CshHintGraphicParam, CshHintStore &);

class ShapeCollection;
class StyleSetBase;

/** How shall we filter the list of hints (by the word under the cursor).*/
enum class EHintFilter {
    None = 0,   ///<No filtering: all hints are displayed
    Fuzzy,      ///<Drop hints not (fuzzy) matching the word under the cursor, sort by how fuzzy the matches are
    Substr,     ///<Sort hints by how long common substring the hint has with the cursor
    StartsWith, ///<Keep only hints that start with the word under the cursor
    Max = StartsWith
};

/** A class storing hints during and after a csh parse and everything needed to draw the
 * hint popup dialog.
 * Descendants actually contain functions to collect this info.
 *
 * A few words on how hints are collected.
 * Often we realize what is the type of hint in a low-level parser rule, but know no specifics.
 * For example, we have grammar rues describing how an attribute looks like, that is "name = value".
 * In this rule we can realize that the cursor is inside value (or we match to a rule saying
 * "name =" and realize that the cursor is just after). Here we can conclude that the hint
 * will be a sort of attribute value. (So we pick EHintSourceType::ATTR_VALUE.) But we do not know
 * what values the attribute can take, because that depends on what element we are parsing.
 * So after we match
 * the low level rule, we set the hint type only and when processing the higher level rule for the
 * full element, do we fill in the actual hints. In the meantime we set the hintStatus member to
 * HINT_LOCATED. Some cases we cannot fill in all the hints in one go and must go to an even higher
 * rule, then we use HINT_FILLING. Specifically for attribute values we also store the name
 * of the attribute in the low level rule into hintAttrName because it is needed in the high
 * level rule to select which attribute we are hinting the values of.*/
class CshHintStore
{
public:
    /** What shapes do we have available prior csh parsing (pre-defined shapes) - use them to draw, as well. */
    const ShapeCollection* pShapes = nullptr;
    /** @name The collected Hint info
    * @{ */
    std::vector<CshHint> Hints;       ///<The collected hints
    bool              was_partial;    ///<Indicates if the cursor is at the end of a partial keyword. On Sel Change we need to re-csh
    bool              allow_anything; ///<If true the user can define anything here besides the hints. Do not autocomplete a hint as she types.
    EHintSourceType   hintSource;     ///<The type of situation we found the cursor is at and for which the hint is provided. Used also in status HINT_LOCATED.
    bool              hadEscapeHint;  ///<True, if we had escape hints - we select character under the cursor and next char differently in MiniEditor.
    bool              hadFileHint;    ///<True, if we had file hints - we will not trim hinted sting to the cursor only in MiniEditor.
    EHintStatus       hintStatus;     ///<Shows if we have located the hint type and if we have filled in the hints or not
    CshPos            hintedStringPos;///<The actual text location the hints refer to (can be the cursor only, which is a zero length range)
    std::string       hintAttrName;   ///<In case of an ATTR_VALUE contains the name of the attribute
    /** @} */
    virtual void clear(); //does not clear the shape pointer
    CshHintStore() { clear(); }
    virtual ~CshHintStore() = default;
    void AllowAnything() { allow_anything = true; } ///<Convenience function
    void CopyHintsFrom(const CshHintStore& csh);
};

/** Variables governing CSH operations (color scheme, indenting, etc.) */
struct CshParams
{
    int m_II = 4;            ///<instruction indent: how much an instruction is indented inside a {} block. The default tab size, too.
    int m_IB = 0;            ///<block indent: how much a block of a block series is indented compared to the first one.
    int m_IM = 2;            ///<middle indent: how much non-attribute elements inside an instruction are indented compared to the first line of the instruction
    int m_IA = 2;            ///<attribute indent: how much attributes are indented compared to the [
    bool m_bSIText = true;   ///<True, if we indent labels specially
    bool m_bSIAttr = true;   ///<True, if we indent attributes specially
    int color_scheme=2;      ///<A pointer to the color scheme to use or nullptr if unknown. Used to ignore CSH entries same as COLOR_NORMAL.
    std::string ForcedDesign;///<What design is forced on us (so its colors and styles can be offered)
    std::set<std::string> fontnames;///<A list of font names for hinting
};

class Csh;

/** Stores information about an inlined region.*/
struct CshInLine {
    CshPos pos;               ///<Location of the included text in the parent chart (with a whitespace-only last line removed)
    std::unique_ptr<Csh> csh; ///<The CSH'ed version of the included text
    CshPos full_pos;          ///<Location between the INLINE_OPEN_STR and INLINE_CLOSE_STR tokens (as originally comes from parsing)
    CshInLine(const CshPos &pos, std::unique_ptr<Csh> &&csh, const CshPos &full_pos) noexcept : pos(pos), csh(std::move(csh)), full_pos(full_pos) {}
    CshInLine(const CshInLine& o);
    CshInLine(CshInLine&&) noexcept = default;
};

class LanguageCollection;

/** A class containing Hints and Color Syntax Highlighting info.
 * Descendants contain code to actually fill this in.
 * This is a class, that is expensive to construct. Better allocate once and re-use.*/
class Csh : public CshHintStore
{
protected:
    bool cursor_at_line_begin = false;     ///<Tells if cursor is at the leading whitespace of a line or in the first keyword.

    /** @name Options, keywords, attributes to color (and hint)
    * @{ */
    std::shared_ptr<const StyleSetBase> default_styles;         ///<A collection of styles to get hints
    std::set<std::string> option_names;
    std::set<std::string> attribute_names;
    std::set<std::string> keyword_names;
    static unsigned FindPrefix(const std::set<std::string> &set, std::string_view text) noexcept;
    static unsigned FindPrefix(const StyleNameSet &set, std::string_view text) noexcept;
    template <unsigned STRING_LEN>
    static unsigned FindPrefix(const char set[][STRING_LEN], std::string_view text) noexcept;
    template <typename range>
    static unsigned FindPrefix(const range& set, std::string_view text) noexcept;
    void MoveHintsToNameCollection(std::set<std::string>& c);
    virtual void FillNamesHints() = 0;
    /** @} */
    virtual bool CalculateIsCursorAtLineBegin() const;
    virtual void BeforeYaccParse(std::string&& input, CharByteIndex first_pos, CharByteIndex cursor_pos);
    virtual void AfterYaccParse();
    virtual bool DoHintLocated(EHintSourceType hsource, std::string_view a_name);
public:
    /** Register the language elements to recognize (part of construction)
    *  @{  */
    /** Register the current list of hints as option names to color. */
    void MoveHintsToOptionNames() { MoveHintsToNameCollection(option_names); }
    /** Register the current list of hints as attribute names to color. */
    void MoveHintsToAttrNames() { MoveHintsToNameCollection(attribute_names); }
    /** Register the current list of hints as keyword names to color. */
    void MoveHintsToKeywordNames() { MoveHintsToNameCollection(keyword_names); }
    /** @} */

    /** @name Input parameters to the color parsing process
    * @{  */
    /**A callback procedure to provide us with the list of files in a directory.
    * @param prefix the part of the filename already typed, we return all filenames not
    *        only the ones prefixed by this.
    *        May include a path component, in that case, we return files from that dir.
    * @param included_from the path of the current file (for relative paths in prefix).*/
    typedef std::vector<std::string>(*FileListProc)(std::string_view prefix, std::string_view included_from);

    StyleNameSet          ForbiddenStyles; ///<Styles we never offer as hints (e.g., ->)
    CharByteIndex         cursor_pos;      ///<The location of the cursor now (used to identify partial keyword names & hint list). The zero-based index of the *byte* (and not UTF-8 character) the cursor is before. If ==input_text.length(), we are after the last character. Larger or negative values are invalid.
    std::shared_ptr<CshParams> params;     ///<Parameters to use
    std::string           FileName;        ///<The disk path of the file we parse - to provide correct include filename hints. May be empty if we are sure to ignore hints or if no disk file.
    FileListProc const    file_list_proc;  ///<The callback to call for a list of files to "include"
    const LanguageCollection* const languages;///<The languages supported by the current system
    /** @} */
    /** @name Running variables during csh parsing
     * @{  */
    std::set<std::string, std::less<>> EntityNames;      ///<The names of entities defined so far
    std::set<std::string, std::less<>> RefNames;         ///<The names of references defined so far
    std::list<CshContext> Contexts;         ///<The context stack
    std::vector<std::pair<std::string, std::vector<std::string>>>
        shape_names;                        ///<list of shape names we have parsed definition for, but did not store, mapped to their port names
    std::string_view      last_inline_type; ///<Save the kind of inlining we last attempted
    CshPos                last_inline_pos;  ///<Where the language name of the last inline specified
    bool                  addRefNamesAtEnd; ///<Set to true if at the end of the csh parsing we shall add the name of the references to the hint list.
    std::string           addEntityNamesAtEnd; ///<Set to a format string if at the end of the csh parsing we shall add the name of the entities to the hint list. The format string shall have a single %s, where the entity name will be inserted and the resulting text used as description.
    std::string           exclude_entity_hint; ///<If the cursor is in an identifier where an entity can be defined and the block name is not yet defined, it is stored here: do not add that to the list of entity hints.
    bool                  hintsForcedOnly;  ///<Set to true if hint is located such that it should be displayed only if user forces that by Ctrl+Space
    std::string           input_text;       ///<The text we parse
    CharByteIndex         first_pos;        ///<The (1-based) position of the first character of 'input_text' in its input file.
    /** @}*/
    /** Type to store designs in. (These designs are just to maintain info needed for coloring and hints, not full attribute sets.)*/
    using CshContextMap = std::map<std::string, CshContext, std::less<>>;
    /** @name The collected CSH info
     * @{ */
    CshListType       CshList;        ///<The collected color syntax info
    CshPosList        ColonLabels;    ///<The position of colon labels
    MultiList         MultiElements;  ///<The recent fragments of multi-strings (string concatenated by ~). See multi_segment_string for more.
    CshPosList        Instructions;   ///<The position of all things that end in a semicolon (arcs, commands, chart options)
    CshPosList        BracePairs;     ///<The position of all {} pairs (the trailing '}' may be in fact missing).
    CshPosList        SqBracketPairs; ///<The position of all [] pairs (the trailing ']' may be in fact missing).
    CshPosList        IfThenElses;    ///<The position of all if/then/else instructions
    CshErrorList      CshErrors;      ///<The collected errors
    CshContextMap     FullDesigns;    ///<The names and content of full designs defined so far
    CshContextMap     PartialDesigns; ///<The names and content of partial designs defined so far
    std::vector<CshInLine> InLines;   ///<The regions where we inline chart text of another kind.
    /** Shifts all CshPos values collected as if we have inserted/deleted 'offset' chars/bytes at 'start'.
     * We cannot move inlines, so if there is any, we return true. False on success.*/
    bool AdjustPositions(CharByteIndex start, CharByteIndex offset) {
        if (InLines.size()) return true;
        CshPos::AdjustList(CshList, start, offset);
        CshPos::AdjustList(ColonLabels, start, offset);
        CshPos::AdjustList(MultiElements, start, offset);
        CshPos::AdjustList(Instructions, start, offset);
        CshPos::AdjustList(BracePairs, start, offset);
        CshPos::AdjustList(SqBracketPairs, start, offset);
        CshPos::AdjustList(IfThenElses, start, offset);
        CshPos::AdjustList(CshErrors.error_ranges, start, offset);
        return false;
    }
    /** @} */

    /** Initializes the Csh Object.
     * @param [in] defaultDesign Specifies a ContextBase to collect
     *   forbidden and default style names, color names and definitions to learn
     *   from.
     * @param [in] proc A procedure to call when we need to list file candidates to the 'include' command.
     * @param [in] param An opaque value to pass to the 'proc'.*/
    explicit Csh(const Context &defaultDesign, FileListProc proc, const LanguageCollection* languages);
    /** Create a copy of the current Csh object (full copy of a descendant).
     * It is used in GUIs to clone a Csh containing the parsed designs and
     * keywords, options, etc. before each parsing of the chart text.
     * TODO: This copy could be elided if we could restore an existing Csh
     * object to its state before the last parse of the chart text.*/
    virtual std::unique_ptr<Csh> Clone() const = 0;
    void clear() override;
    ~Csh() override = default;

    /** Parse chart text for color syntax and hint info
    * @param [in] input The chart text. It may be a fragment of a file, e.g., for inlined charts.
    * @param [in] first_char The (1-based) index of the first character of 'input' in the file we parse. 
    *             If we parse a whole file, it shall be 1.
    * @param [in] cursor_p The current position of the cursor.
    * @param [in] pedantic The starting value of 'pedantic' chart option.*/
    virtual void ParseText(std::string&& input, CharByteIndex first_char, CharByteIndex cursor_p, bool pedantic) = 0;
    /** Parse design library text to extract designs and shapes
     * By default it is equivalent to ParseText(), but may tweak settings
     * (restoring them afterwards).
     * @param [in] input The design library text */
    virtual void ParseDesignText(std::string&& input) { ParseText(std::move(input), CharByteIndex{}, CharByteIndex::invalid(), true); }

    //fill in size, plain and filter/compact if needed
    virtual void ProcessHints(Canvas &canvas, StringFormat *format, std::string_view uc, EHintFilter filter_by_uc, bool compact_same);

    /** Register the requested language of a potential inlining block.
     * @returns true if we have such a valid language, but we store the params even if we dont. */
    bool NoteInliningLanguage(std::string_view lang_name, const CshPos& pos);
    /**Add a name of a shape and its port names (without the actual shape)
     * returns false if a shape with this name is already added (in that case we don't add it)*/
    bool AddShapeName(std::string_view n, std::vector<std::string> *ports = nullptr);
    /** Test if a shape name is valid or not.*/
    bool IsShapeName(std::string_view n) const { return GetShapeNum(n)>=0; }
    /** Returns the number of the shape. -1 if not a shape name.*/
    int GetShapeNum(std::string_view n) const;
    /** @name Functions to add/remove a CSH entry
    * @{  */
    void AddCSH(const CshPos&, EColorSyntaxType); ///<The basic variant: color a range to this language element type
    void AddCSH_Error(const CshPos&pos, std::string &&text) { CshErrors.Add(pos, std::move(text)); } ///<The basic variant for errors: add error "text" at this location
    void AddCSH_ErrorAfter(const CshPos&pos, std::string &&text); ///<Add an error just after this range
    virtual void AddCSH_KeywordOrEntity(const CshPos&pos, std::string_view name);
    virtual void AddCSH_ColonString_CheckAndAddEscapeHint(const CshPos& pos, std::string_view value, bool unquoted, bool two_colons=false);
    virtual void AddCSH_AttrName(const CshPos&, std::string_view name, EColorSyntaxType); ///<At pos there is either an option or attribute name (specified by the type). Search and color.
    virtual void AddCSH_AttrValue_CheckAndAddEscapeHint(const CshPos& pos, std::string_view value, std::string_view name);
            void AddCSH_AttrValue_CheckAndAddEscapeHint_Solo(const CshPos& pos, std::string_view value);
    virtual void AddCSH_AttrColorValue(const CshPos& pos); ///<At pos there is an attribute value that looks like a color definition (with commas and all).
    virtual void AddCSH_StyleOrAttrName(const CshPos&pos, std::string_view name); ///<At pos there is either an attribute name or a style. Decide and color.
    virtual void AddCSH_EntityName(const CshPos&pos, std::string_view name); ///<At pos there is an entity name. Search and color.
    virtual void AddCSH_AllCommentBeyond(const CshPos& pos); ///<Mark anything beyond the end of 'pos' as comment
            void AddCSH_ParamOrCond(const CshPos& pos, const multi_segment_string& str); //Color parameter values on proc replay or ifthenelse conditions
    void ReplaceCSH(const CshPos &pos, EColorSyntaxType from, EColorSyntaxType to); ///<Replace any csh entry colored 'from' and *fully within* 'pos' to 'to'
    void RemoveAllTouchingCSH(const CshPos &pos);
    bool IsAnyColoringIn(const CshPos &pos) const { return CshList.CheckIfOverlap(pos); }
    /** Sets addEntityNamesAtEnd to the parameter to add all entities at the end of the csh run.
     * Done in a virtual function so that descendants can override and add more info.*/
    virtual void AddEntityNamesAtTheEnd(std::string_view msg) { addEntityNamesAtEnd = msg; }
    /** @}*/

    /** @name Functions to register info (mostly for precise indenting)
    * @{  */
    virtual void AddInLine(std::string_view text, const CshPos& pos, bool pedantic);
    /** Adds the range to the list of instructions. Used for auto-indenting to the head of it.*/
    void AddInstruction(const CshPos& pos) { _ASSERT(pos.IsValid());  Instructions.push_back(pos); ClearMulti(); } //to avoid positions of zero (can be if an instruction starts with a potentially empty rule as with DOT
    /** Adds the range to the list of instructions, if this range does not represent a curly brace pair.
     * Used in ifthenelse. There we do not want braces to become their own instruction.*/
    void AddInstructionIfNotBrace(const CshPos &pos) { if (std::find(BracePairs.rbegin(), BracePairs.rend(), pos) == BracePairs.rend()) AddInstruction(pos); }
    /** Adds a brace pair that is missing its closing element.
     * We substitute a char beyond the end-of-file as the closing element, this enables
     * auto-indent to assume we are inside a brace pair even when cursor is at EOF.*/
    void AddOpenBracePair(const CshPos &pos) { BracePairs.emplace_back(pos.first_pos, CharByteIndex::max()); }
    /** Adds a range to the list of colon labels. Used for indenting. */
    void AddColonLabel(const CshPos &pos, std::string_view /*text*/) { ColonLabels.push_back(pos); }
    void AddQuotedString(const CshPos&) { } ///<Marks a range as a quoted string
    //bool IsQuotedString(const CshPos& pos, std::string_view value) const noexcept { _ASSERT(input_text.data() <= value.data() && value.data() <= input_text.data() + input_text.size()); return input_text.data() < value.data() && value.data()[-1] == '"'; }
    bool IsQuotedString(const CshPos& pos) const noexcept { return first_pos.byte_index<pos.first_pos.byte_index && pos.first_pos.byte_index<first_pos.byte_index+(int)input_text.size() && input_text[pos.first_pos.byte_index-first_pos.byte_index]=='"'; }
    void StoreMulti(const str_view& s, const CshPos& pos) { if (std::ranges::find(MultiElements, pos)==MultiElements.end()) MultiElements.emplace_back(pos, s); }
    void StoreMulti(const multi_segment_string& s, const CshPos& pos) { if (!s.had_error && !s.multi && std::ranges::find(MultiElements, pos)==MultiElements.end()) MultiElements.emplace_back(pos, s.view(), s.had_param); }
    void ClearMulti() noexcept { MultiElements.clear(); }
    const CshPos *IsInRange(const CshPosList &range, int char_index, bool include_after) const;
    const CshPos *IsInsideRange(const CshPosList &range, int char_index) const;
    const CshPos *IsAtEndOfRange(const CshPosList &range, int char_index) const; ///<After parsing return the range if character 'pos' is the last of one of the 'range' elements. (Used for smart ident.)
protected:
    CharByteIndex FindLineBeginFrom(CharByteIndex pos) const;
    int ColOf(CharByteIndex pos) const;
    int FirstNonWhitespaceIndent(CharByteIndex line_begin) const;
    int FindIndentOfNonWhitespaceAfter(CharByteIndex pos) const;
public:
    int FirstCurrentLineIndent(CharByteIndex pos) const;
    int FindProperLineIndent(CharByteIndex pos, bool include_after, int* current_indent = nullptr, CharByteIndex* line_begin = nullptr, int first_line_indent = 0) const;
    /** @}*/

    /** Returns the current context. */
    CshContext &CurrentContext() { return Contexts.back(); }
    /** Returns the current context. */
    const CshContext &CurrentContext() const { return Contexts.back(); }
    /** Returns if we shall ignore content being parsed. If so, we shall ignore anything defined (colors, entities, nodes, etc.). */
    bool SkipContent() const { return Contexts.back().SkipContent(); }
    /** Push the context stack copying what was on top.*/
    void PushContext() { Contexts.push_back(Contexts.back());}
    /** Push the context stack  with an empty context.*/
    void PushContext(bool f, EContextParse p) { Contexts.emplace_back(f, p); }
    void PopContext() { Contexts.pop_back(); }  ///<Pop the context stack.
    std::string SetDesignTo(std::string_view design, bool full);
    /** Returns the relation of the cursor pos to a range in the input file.*/
    ECursorRelPosType CursorIn(const CshPos& p) const noexcept { return CursorIn(p.first_pos.char_index, p.end_pos.char_index); }
    /** Returns the relation of the cursor pos to a range in the input file.*/
    ECursorRelPosType CursorIn(int first_char, int end_char) const noexcept;
    /** True if the cursor stands at the beginning of a line (only whitespace before it).*/
    bool IsCursorAtLineBegin() const noexcept { return cursor_at_line_begin; }

    /** Tells what formatting prefix to append to an information-only hint that should not be inserted to chart text.*/
    std::string HintPrefixNonSelectable() const { return "\\i"; }
    /** Tells what formatting prefix to append to a hint of a given type.*/
    std::string HintPrefix(EColorSyntaxType) const;

    /** @name Set hint status if cursor position is at a hintable place.
    * @{ */
    virtual bool StartsInAlpha(const CshPos &one) const;
    virtual bool EndsInAlpha(const CshPos &one) const;

    bool CheckHintBefore(const CshPos &one, EHintSourceType hsource, std::string_view a_name = {}, bool alpha=true);
    bool CheckHintAt(const CshPos &one, EHintSourceType hsource, std::string_view a_name = {});
    bool CheckHintAfter(const CshPos &one, EHintSourceType hsource, std::string_view a_name = {}, bool alpha = true);
    bool CheckHintBetween(const CshPos &one, const CshPos &two, EHintSourceType hsource, std::string_view a_name = {}, bool alpha = true);
    bool CheckHintBetweenAndAt(const CshPos &one, const CshPos &two, EHintSourceType hsource, std::string_view a_name = {}, bool alpha = true)
         {return CheckHintBetween(one, two, hsource, a_name, alpha) || CheckHintAt(two, hsource, a_name);}

    bool CheckLineStartHintBefore(const CshPos &pos, bool alpha=true);
    bool CheckLineStartHintAt(const CshPos &pos);
    bool CheckLineStartHintAfter(const CshPos &pos, bool alpha = true);
    bool CheckLineStartHintBetween(const CshPos &pos, const CshPos &pos2, bool alpha = true);

    bool CheckEntityHintBefore(const CshPos &one);
    bool CheckEntityHintAt(const CshPos &one);
    bool CheckEntityHintAfter(const CshPos &one);
    bool CheckEntityHintBetween(const CshPos &one, const CshPos &two);
    bool CheckEntityHintBetweenAndAt(const CshPos &one, const CshPos &two)
        { if (CheckEntityHintBetween(one, two)) return true; return CheckEntityHintAt(two); }

    bool CheckHintLocated(EHintSourceType hsource, const CshPos  &location_to_check);
    bool CheckHintLocated(EHintSourceType hsource);
    bool CheckHintLocated(const CshPos  &location_to_check);

    void ClearHints() { hintStatus = HINT_NONE; Hints.clear(); }
    /** @}*/

    /** @name Add specific items to the list of hints.
    * None of these sets hintSource, hintStatus, hintsForcedOnly
    * @{ */
    void AddToHints(CshHint &&h);
    void AddToHints(const CshHint &h) { AddToHints(CshHint(h)); } ///<Insert a hint to the list of hints.
    void AddToHints(const std::set<CshHint> &hints) { for (auto &h: hints) AddToHints(h); }
    void AddToHints(const char * const * names_descriptions,
                    std::string_view prefix, EHintType t,
                    CshHintGraphicCallback c = nullptr, bool in_order = false, bool selectable = true);
    void AddToHints(const char * const * names_descriptions,
                    std::string_view prefix, EHintType t,
                    CshHintGraphicCallback c, CshHintGraphicParam, bool selectable = true);
    void AddToHints(const std::map<std::string, std::string> &names_descriptions,
                    std::string_view prefix, EHintType t,
                    CshHintGraphicCallback c, CshHintGraphicParam, bool selectable);
    void AddToHints(const char names[][ENUM_STRING_LEN], const char * const descriptions[],
                    std::string_view prefix, EHintType t,
                    CshHintGraphicCallback c = nullptr, bool selectable = true);
    void AddToHints(const char names[][ENUM_STRING_LEN], const char * const descriptions[],
                    std::string_view prefix, EHintType t,
                    CshHintGraphicCallback c, CshHintGraphicParam, bool selectable = true);
    virtual void AddColorValuesToHints(bool define);
    void AddDesignsToHints(bool full);
    void AddStylesToHints(bool include_forbidden, bool define);
    void AddPortsToHints(const std::string &shape) { AddPortsToHints(GetShapeNum(shape)); }
    void AddPortsToHints(int shape);
    void AddCompassPointsToHints();
    void AddShapesToHints();
    void AddYesNoToHints();
    void AddIncludeFilesToHints(std::string_view text = {}, const CshPos& pos = CshPos::invalid());
    void AddLanguagesToHints(std::string_view what);
    virtual void AddEntitiesToHints();
    void AddEscapesToHints(EEscapeHintType);
    /** Add keywords and option names (and whatever may start an instruction) to hint list.*/
    virtual void AddLineBeginToHints();
    /** Add chart options to hints. */
    virtual void AddOptionsToHints() { AddDesignOptionsToHints();  }
    /** Add keywords and option names applicable inside a design definition to hint list.*/
    virtual void AddDesignLineBeginToHints();
    /** Add chart options applicable inside a design definition to hints. */
    virtual void AddDesignOptionsToHints() { }
    virtual void AttributeNamesForStyle(std::string_view style);
    virtual bool AttributeValuesForStyle(std::string_view attr, const std::string &style);
    /** @}*/

    /** A function to clean up elements after parsing a design library.
     * Only elements are kept that need to be carried over from a design library to the actual charts.
     * These include the designs parsed, the shape library (set after parsing the design library
     * language-wise, not just for coloring as we do in this class), the font name and scheme to use
     * pointer and the forbidden styles. */
    virtual void CleanupAfterDesignlib();

    /** Returns the char/byte index of a character index based on 'input_text' and 'first_pos'.
     * It is slow, requires UTF8 parsing of the whole text.
     * @returns an invalid index if 'char_index' is before 'first_pos' or after the 'input_text' or if input_text is not valid UTF-8. */
    CharByteIndex PosOfChar(int char_index) const;

    /** If 'char_index' is inside hintedStringPos, we truncate it to the part before 'char_index'*/
    void TrimHintedStringPos(int char_index);

    /** @name Entity name replace
    * Helpers to help replace elements
    * @{ */
    struct EntityNameUnderResult
    {
        std::string_view full_entity_name; ///<The full name of the entity under the cursor (UTF-8), that is, it may be longer than the actual text under the cursor. Empty if none. It points to Csh::input_text or to some other Csh member of 'csh' below.
        int byte_pos = 0;                  ///<The byte (and not character) index of the clicked character within 'name'. E.g., zero if the user clicked the first character.
        const Csh* csh = nullptr;          ///<The Csh of the name (which may be different from the top chart in case of inlining. Can be nullptr if 'name' is empty.
        CshPos pos_inline{};               ///<In case of an inlined chart, the range of the inlined chart text. Empty if not an inlined chart
        explicit constexpr operator bool() const noexcept { return !full_entity_name.empty(); }
    };
protected: 
    /** Extracts the entity name under the cursor, provided it is in an inlined chart.
     * This is a helper to EntityNameUnder(). */
    EntityNameUnderResult EntityNameUnderInLined(long char_index) const;
public:
    /** Extracts the entity name under 'char_index'. */
    virtual EntityNameUnderResult EntityNameUnder(long char_index) const;
    /** Given a full entity name and the character offset of the clicked char within it,
     * what name should we ask the user to replace. For Signalling Charts and Graphs
     * entity names are not structured, so the whole of it will be replaced. For Block
     * Diagrams, we just modify the segment under the cursor.
     * Input parameters are what was returned by EntityNameUnder. */
    virtual std::string_view AskReplace(std::string_view full_entity_name, int byte_pos [[maybe_unused]]) const { return full_entity_name; }
    /** Returns the chart text with a certain entity name replaced.
     * Takes what is returned by EntityNameUnder and what to replace the
     * clicked segment (for block diagrams) or the full entity name (signalling & dot) to.
     * @param [in] full_entity_name The full name of the entity to replace. May be different from
     *             what is under the cursor.
     * @param [in] byte_pos The byte offset of the clicked character inside 'full_entity_name' as returned by EntityNameUnder().
     * @param [in] replace_to Whatever the user wanted as new name.
     * @param [in] lStart The first char_index of the selection.
     * @param [in] lEnd The one beyond the last char_index of the selection. Equals to lStart if no selection (replace in whole file). */
    virtual std::string ReplaceEntityName(std::string_view full_entity_name, int byte_pos,
                                          std::string_view replace_to,
                                          long lStart, long lEnd) const;
    /** @}*/
};

inline CshInLine::CshInLine(const CshInLine& o) : pos(o.pos), csh(o.csh->Clone()), full_pos(o.full_pos) {}


int CaseInsensitiveBeginsWith(std::string_view a, std::string_view b) noexcept; //defined in attribute.h for real;

/** Finds a text in a collection - or its prefix if no exact match.
@returns 0 if no match, 1 if text is a prefix of one of the entries,
2 if text is one of the entries.*/
template <unsigned STRING_LEN>
unsigned Csh::FindPrefix(const char set[][STRING_LEN], std::string_view text) noexcept
{
    unsigned ret = 0;
    for (unsigned i = 0; set[i][0]; i++) {
        unsigned match = CaseInsensitiveBeginsWith(set[i], text);
        if (match==2) return 2;
        ret = std::max(ret, match);
    }
    return ret;
}

/** Finds a text in a collection - or its prefix if no exact match.
@returns 0 if no match, 1 if text is a prefix of one of the entries,
2 if text is one of the entries.*/
template <typename range>
unsigned Csh::FindPrefix(const range &set, std::string_view text) noexcept
{
    unsigned ret = 0;
    for (auto &element : set) {
        unsigned match = CaseInsensitiveBeginsWith(element, text);
        if (match == 2) return 2;
        ret = std::max(ret, match);
    }
    return ret;
}




/** Emulate inserting or removing offset chars after start & update CSH entries
 * We cancel entries that are entirely in the deleted region (if offset < 0) by setting their
 * last position to a value smaller than their first pos.
 * @param list The list to adjust. Any container, shall contain CshPos (or derivative) elements.
 * @param [in] start The first character of the change. When inserting we insert *before* this character;
 *                   when deleting, this is the first character we delete.
 * @param [in] offset The number of characters/bytes inserted (for positive value) or removed (for
 *                    negative value). Value zero requires nothing to do.*/
template<class PosList>
constexpr void CshPos::AdjustList(PosList &list, CharByteIndex start, CharByteIndex offset)
{
    _ASSERT(offset.IsValidDiff());
    _ASSERT(start.IsValid());
    if (offset.is_null()) return;
    const int upper = offset.char_index < 0 ? start.char_index - offset.char_index : start.char_index;
    for (auto &csh : list) {
        //if the entry is completely before "start" we do nothing
        if (csh.end_pos.char_index <= start.char_index) continue;
        //if the entry is entirely after the affected region
        //we just shift it.
        if (csh.first_pos.char_index >= upper) {
            csh.first_pos += offset;
            csh.end_pos += offset;
            continue;
        }
        if (offset.char_index>0) {
            //we are inserting. here 'upper==start' thus it must be that
            //the entry begins before the insertion point and ends after
            //('first pos < start' and 'end pos > start'.)
            //Make this entry longer
            csh.end_pos += offset;
            continue;
        }
        //here we delete between start and upper (but not upper itself)
        if (csh.first_pos.char_index >= start.char_index)
            csh.first_pos = start; //first pos is in the region deleted
        if (csh.end_pos.char_index <= upper)
            csh.end_pos = start;   //last pos is in the region deleted
        else
            csh.end_pos += offset; //last pos is beyond the region deleted
    }
}

/** Calculates the difference between two CshEntries list and stores into 'this'.
*  That is what is new in new_list compared to old_list.
*  Entries that have disappeared will be retained using 'neutral' color.
* Assumes that both input lists are ordered and non-overlapping and this is,
* how the resulting list will be.*/
template <typename PosList1, typename PosList2>
inline void CshListType::DiffInto(const PosList1 &old_list, const PosList2 &new_list,
                                  EColorSyntaxType neutral) {
   //A reminder about positions: first_pos==last_pos indicates a single char range.
   //We assume here that neither old_list nor new_list have overlapping entries
   //and we will produce a delta which also has none.
    auto o = old_list.begin();
    auto n = new_list.begin();
    CharByteIndex done_till; //indicates the index beyond the last position we have handled in 'delta'
    while (o!=old_list.end() && n!= new_list.end()) {
        //ignore empty ranges in the old list (which can happen due to delete)
        //also ignore if the complete old entry is before we have completely OK
        if (o->empty() || o->end_pos <= done_till) {
            o++;
            continue;
        }
        const CharByteIndex o_first = std::max(done_till, o->first_pos);
        if (o_first == n->first_pos) {
            if (o->color != n->color) //begins at same position, but different color: keep new version
                AddToBack(*n);
            else if (o->end_pos==n->end_pos)
                o++; //equal entries - sort of fast path
            else if (o->end_pos<n->end_pos)
                //old entry is shorter, add that part of 'n' that
                //is beyond of 'o'
                AddToBack(CshEntry(o->end_pos, n->end_pos, n->color));
            done_till = n->end_pos;
            n++;
        } else if (o_first < n->first_pos) {
            //a (perhaps partially) removed item: paint with normal color
            //up to the beginning of 'n'
            done_till = std::min(o->end_pos, n->first_pos);
            AddToBack(CshEntry(o_first, done_till, neutral));
            if (done_till >= o->end_pos)
                o++;
        } else {//n->first_pos < o->first_pos
            AddToBack(*n);
            done_till = n->end_pos;
            n++;
        }
    }
    while (o!=old_list.end()) {
        if (o->end_pos > done_till)
            AddToBack(CshEntry(std::max(done_till, o->first_pos), o->end_pos, neutral));
        o++;
    }
    while (n!=new_list.end()) {
        AddToBack(*n);
        n++;
    }
}


std::string Cshize(std::string_view input, const CshListType &cshList, unsigned cshStyle,
                   std::string_view textformat);


/** A dummy Csh class, not doing any coloring, but one that can be instantiated.
 * Can be used by languages that do not support coloring and hints.*/
class DummyCsh : public Csh
{
public:
    DummyCsh(const LanguageCollection* languages);
    ~DummyCsh() override = default;
    std::unique_ptr<Csh> Clone() const override { return std::make_unique<DummyCsh>(*this); }
    void FillNamesHints() override {};
    void ParseText(std::string&& input, CharByteIndex first_char, CharByteIndex cursor_p, bool) override {
        BeforeYaccParse(std::move(input), first_char, cursor_p);
        AfterYaccParse();
    }
};

#endif


