// -*- mode: c++; c-file-style: "linux"; c-basic-offset: 2; indent-tabs-mode: nil -*-
//
//  Copyright (C) 2006-2015 Andrej Vodopivec <andrej.vodopivec@gmail.com>
//            (C) 2012 Doug Ilijev <doug.ilijev@gmail.com>
//            (C) 2014-2015 Gunter Königsmann <wxMaxima@physikbuch.de>
//
//  This program is free software; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation; either version 2 of the License, or
//  (at your option) any later version.
//
//  This program 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 General Public License for more details.
//
//
//  You should have received a copy of the GNU General Public License
//  along with this program; if not, write to the Free Software
//  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
//  SPDX-License-Identifier: GPL-2.0+

#ifndef EDITORCELL_H
#define EDITORCELL_H

#include "Cell.h"
#include "FontAttribs.h"
#include "MaximaTokenizer.h"
#include <vector>
#include <algorithm>
#include <list>
#include <unordered_map>

struct BidiRun;

/*! \file

  This file contains the definition of the class EditorCell
*/

/*! This class defines what the user sees as input cell

  This class handles input cells including:
  - The per-cell undo buffer
  - The handling of key presses when this cell is active

  Since Unicode doesn't provide us with a "soft linebreak" letter we
  use <code>\\r</code> as a marker that this line has to be broken here if we
  don't want it to extend beyond the right margin of the screen.

  In a few places we use wxString::const_iterator instead of accessing individual
  letters within the string using the [] operator. This might look overly
  complicated. But in UTF-8 all non-standard-ASCII-characters use more than one
  byte making iterating over every single char of the string the only way of
  determining which address char n is at. An iterator is the only way of not
  having to determine the address of every single char independently.

  \todo Draw only tokens that are in the redraw region.
*/
class EditorCell : public Cell
{
private:
#if wxUSE_ACCESSIBILITY
  wxAccStatus GetName (int childId, wxString *name) const override;
  wxAccStatus GetDescription(int childId, wxString *description) const override;
  wxAccStatus GetFocus (int *childId, Cell **child) const override;
  wxAccStatus GetDefaultAction(int childId, wxString *actionName) const override;
  wxAccStatus GetValue (int childId, wxString *strValue) const override;
  wxAccStatus GetRole (int childId, wxAccRole *role) const override;
  wxAccStatus GetState (int childId, long *state) const override;
#endif

public:
  //! The constructor
  EditorCell(GroupCell *group, Configuration *config, wxString text = {});
  EditorCell(GroupCell *group, const EditorCell &cell);

  /*! Construct the EditorCell subtype (code vs. text/sectioning) matching type.

    All EditorCells should be created through this factory rather than
    constructed directly, so the concrete subclass always matches the cell's
    role. Code cells (MC_TYPE_INPUT, and the editor Maxima's questions are
    answered in) become one subclass; text and all sectioning levels become the
    other. See the EditorCell-split plan.
   */
  static std::unique_ptr<EditorCell> Create(GroupCell *group,
                                            Configuration *config, CellType type,
                                            wxString text = {});

  //! Is `type` a code (Maxima-input) cell type, as opposed to text/sectioning?
  static bool IsCodeType(CellType type);

  /*! Does this concrete cell edit code (Maxima input) rather than prose?

    Fixed by the subclass, so it stays correct even if m_type is momentarily out
    of sync. Used when a cell's type is changed to decide whether the editable
    must be rebuilt as the other subclass. The base is prose by default;
    CodeEditorCell overrides it.
   */
  virtual bool IsCodeEditor() const { return false; }
  void UpdateSelectionString();
  // The selection/cursor setters clamp their argument to a valid index into the
  // current text via ClampToText(), so m_selectionStart / m_selectionEnd are
  // always in [0, m_text.Length()] (e.g. CursorMove(-n) past the start clamps to
  // 0 instead of wrapping a size_t to a huge value). The getters below keep a
  // defensive std::min as well, because the text can still shrink *after* a
  // selection was set without the selection being updated.
  void SetSelection(size_t start, size_t end){m_selectionStart = ClampToText(start); m_selectionEnd = ClampToText(end); UpdateSelectionString();}
  bool SelectionActive() const {return m_selectionStart != m_selectionEnd;}
  void ClearSelection() {SelectionEnd(SelectionEnd());}
  void SelectionStart(size_t start) {m_selectionStart = ClampToText(start); UpdateSelectionString();}
  void SelectionEnd(size_t end) {m_selectionEnd = ClampToText(end); UpdateSelectionString();}
  size_t SelectionStart() const {return std::min(m_selectionStart, m_text.Length());}
  size_t SelectionEnd() const {return std::min(m_selectionEnd, m_text.Length());}
  size_t SelectionLeft() const {return std::min(SelectionStart(), SelectionEnd());}
  size_t SelectionRight() const {return std::max(SelectionStart(), SelectionEnd());}
  size_t SelectionLength() const {return(SelectionEnd()-SelectionStart());}
  void  SelectionLength(size_t length) {SelectionEnd(SelectionStart() + length);  UpdateSelectionString();}
  void CursorMove(long long increment) {m_selectionEnd = ClampToText(static_cast<long long>(m_selectionEnd) + increment);
    m_selectionStart = m_selectionEnd; UpdateSelectionString();}
  size_t CursorPosition() const {return std::min(m_selectionEnd, m_text.Length());}
  void CursorPosition(size_t pos) {m_selectionStart = ClampToText(pos);
    m_selectionEnd = m_selectionStart; UpdateSelectionString();}
  const CellTypeInfo &GetInfo() override;
  std::unique_ptr<Cell> Copy(GroupCell *group) const override;

  //! Get the previous EditorCell in the list
  // cppcheck-suppress duplInheritedMember
  EditorCell *GetPrevious() const { return dynamic_cast<EditorCell*>(Cell::GetPrevious()); }
  //! Get the next EditorCell in the list.
  // cppcheck-suppress duplInheritedMember
  EditorCell *GetNext() const { return dynamic_cast<EditorCell*>(Cell::GetNext()); }

  //! Insert the symbol that corresponds to the ESC command txt
  void InsertEscCommand(const wxString &txt) {
    InsertText(InterpretEscapeString(txt));
  }

  //! Get the whole maxima command that is currently under the cursor (including all arguments)
  wxString GetFullCommandUnderCursor();

  //! Add a new parameter to a draw- or similar command including the comma, if needed.
  void AddDrawParameter(wxString param);

  //! May this Editor Cell contain the answer to a question?
  void AutoAnswer(bool autoAnswer){m_autoAnswer = autoAnswer;}

  /*! Remember that this is the cell the search was started in.

    \param index The index of the character the search was started at.
  */
  void SearchStartedHere(size_t index) const;
  //! Remember that this is the cell the search was started in.
  void SearchStartedHere() const;
  //! Remember that this is the cell the mouse selection was started in.
  void MouseSelectionStartedHere() const;
  //! Remember that this is the cell the keyboard selection was started in.
  void KeyboardSelectionStartedHere() const;

  //! A list of words that might be applicable to the autocomplete function.
  const auto &GetWordList() const { return m_wordList; }

  /*! Normalize line endings.

    Collapses literal "\r\n" (Windows CRLF) sequences to "\n". Does NOT touch
    a lone '\r': that is wxMaxima's own soft (word-wrap) line break marker,
    not a line ending, and must survive untouched (see the comment in the
    constructor).
  */
  static wxString NormalizeLineEndings(const wxString &input_);

  //! Escape all chars that cannot be used in HTML otherwise
  static wxString EscapeHTMLChars(wxString input);

  //! Convert all but the first of a row of multiple spaces to non-breakable
  static wxString PrependNBSP(wxString input);

  void Recalculate(const AFontSize fontsize) const override;

  using Cell::SetCurrentPoint;
  void SetCurrentPoint(wxPoint point) const override;
  virtual void Draw(wxDC *dc, wxDC *antialiassingDC) override;

  //! Convert the current cell to HTML code.
  wxString ToHTML() const;
  wxString ToMatlab() const override;
  /*! Convert the current cell to a string

    \param dontLimitToSelection
    - false: If text is selected return only the selected text
    - true:  Always return all text in this text cell
  */
  //! Convert the current cell to Matlab code
  wxString ToMatlab(bool dontLimitToSelection) const;
  //! Convert the current cell to RTF code
  wxString ToRTF() const override;
  wxString ToString() const override;
  wxString ToString(bool dontLimitToSelection) const;
  //! Convert the current cell to LaTeX code
  wxString ToTeX() const override;
  //! Convert the current cell to XML code for inclusion in a .wxmx file.
  wxString ToXML() const override;

  //! Get the font that matches this cell's formatting
  const wxFont &GetFont() const {
    return m_configuration->GetStyle(GetTextStyle())->GetFont(m_fontSize_Scaled);
  }
  //! Set the currently used font to the one that matches this cell's formatting
  void SetFont(wxDC *dc) const;

  //! Sets the current color to this cell's foreground color
  void SetForeground(wxDC *dc);

  /*! Sets the text that is to be displayed.

    Automatically calls StyleText().
  */
  void SetValue(const wxString &text) override;

  /*! Returns the text contained in this cell

    Naturally all soft line breaks are converted back to spaces beforehand.
  */
  const wxString &GetValue() const override { return m_text; }

  /*! The soft (word-wrap) line-break offsets of the last styling pass.

    Each entry is the content offset at which a display line begins because the
    text was word-wrapped (see \ref m_softBreaks). Exposed for tests and
    inspection; the breaks are derived layout data and never appear in
    \ref GetValue().
  */
  const std::vector<std::size_t> &GetSoftBreaks() const { return m_softBreaks; }

  /*! Sets the character ranges of this cell's text the diff viewer wants
    painted with the TS_DIFF_CHANGED background (the parts the matching cell
    of the other file doesn't contain).

    Each range is half-open ([start, end)) and indexes into GetValue(). The
    ranges survive re-layout: soft word-wrapping swaps ' ' and '\r' in place
    and therefore never moves a character. They do NOT survive edits, so
    Draw() ignores them as soon as the text's length changes and SetValue()
    drops them entirely.
  */
  void SetDiffHighlights(std::vector<std::pair<std::size_t, std::size_t>> ranges) {
    m_diffHighlights = std::move(ranges);
    m_diffHighlightsTextLength = m_text.Length();
  }
  //! The ranges set by SetDiffHighlights() (empty outside the diff viewer).
  const std::vector<std::pair<std::size_t, std::size_t>> &GetDiffHighlights() const {
    return m_diffHighlights;
  }

  /*! Converts m_text to a list of styled text snippets that will later be displayed by draw().

    This function also generates a wordlist for this EditorCell so Autocompletion can learn
    about variable names contained in lists or cells that still haven't been evaluated.

    For cells containing text instead of code this function adds a <code>\\r</code> as a marker
    that this line is to be broken here until the window's width changes.
  */
  void StyleText() const;
  /*! Is Called by StyleText() if this is a code cell */
  void StyleTextCode() const;
  void StyleTextTexts() const;

protected:
  /*! The code-vs-prose-specific half of StyleText(), chosen by the subclass.

    The base styles prose (StyleTextTexts()); CodeEditorCell overrides it to
    style Maxima code (StyleTextCode()). This is the virtual dispatch that
    replaced the old `if (m_type == MC_TYPE_INPUT)` switch inside StyleText().
  */
  virtual void StyleTypedText() const;

  /*! Transform text on its way into SetValue(); returns what to store and sets
    cursorPos. The base stores it verbatim (cursor at the end); CodeEditorCell
    overrides it to add Maxima-input conveniences (auto-close a just-typed
    bracket/quote, prefix a leading operator with "%"). Replaced the old
    `if (m_type == MC_TYPE_INPUT)` branch in SetValue().
  */
  virtual wxString PreprocessNewValue(const wxString &text,
                                      std::size_t &cursorPos) const;

  /*! The ` type="..."` attribute ToXML() writes for this cell.

    The base maps the prose/sectioning m_type values (text/title/section/...);
    CodeEditorCell overrides it to always be code (`input`). Replaced the m_type
    switch inside ToXML().
  */
  virtual wxString XMLTypeAttribute() const;

public:
  void Reset();

  //! Decide what to do if the user pressed a key when this cell was selected
  void ProcessEvent(wxKeyEvent &event) override;

  /*! Activate the blinking cursor in this cell

    Automatically calls DeactivateCursor on an eventual cell the cursor currently is in.
    Normally Mathctrl::SetActiveCell() is used in order to get this function called.

    \return true, if recalculation is needed.
  */
  bool ActivateCursor();

  //! Deactivate the blinking cursor in the EditorCell it is in.
  void DeactivateCursor();

  //! Return the index of the 1st char of the line containing the letter pos.
  size_t BeginningOfLine(size_t pos) const;

  //! Return the index of the last char of the line containing the letter \#pos,
  size_t EndOfLine(size_t pos);

  //! Adds a ";" to the end of the last command in this cell in case that it doesn't end in $ or ;
  bool AddEnding() override;

  //! Determines which line and column the pos'th char is at.
  void PositionToXY(size_t position,  size_t *x, size_t *y);

  //! Determines which index the char at the position "x chars left, y chars down" is at.
  size_t XYToPosition(size_t x, size_t y);

  //! The screen coordinates of the cursor
  wxPoint PositionToPoint(size_t pos) override;
  wxPoint PositionToPoint() override {return PositionToPoint(CursorPosition());}

  //! Sets the cursor to the screen coordinate point
  void SelectPointText(wxPoint point) override;

  //! Selects the text between the screen coordinates one and two
  void SelectRectText(wxPoint one, wxPoint two) override;

  //! Selects the word the cursor is currently at.
  wxString SelectWordUnderCaret(bool selectParens = true, bool toRight = true,
                                bool includeDoubleQuotes = false);

  //! Is the point point inside the currently selected text?
  bool IsPointInSelection(wxPoint point);

  bool CopyToClipboard() const override;

  bool CutToClipboard() override;

  void PasteFromClipboard(bool primary = false) override;

  //! Get the character position the selection has been started with
  size_t GetSelectionStart() const
    { return SelectionStart(); }

  //! Get the character position the selection has been ended with
  long GetSelectionEnd() const
    { return SelectionEnd(); }

  //! Select the whole text contained in this Cell
  void SelectAll() override
    {
      SetSelection(0, m_text.Length());
    }

  //! Does the selection currently span the whole cell?
  bool AllSelected() const
    {
      return (SelectionStart() == 0) && (SelectionEnd() == m_text.Length());
    }

  //! Unselect everything.
  void SelectNone()
    {
      ClearSelection();
    }


  bool CanCopy() const override
    {
      return SelectionActive();
    }

  bool FindMatchingQuotes();

  void FindMatchingParens();

  wxCoord GetLineWidth(size_t line, size_t pos);

  //! Pixel x of the next 4-column tab stop at or after startX, where one
  //! column is the width of a space glyph in the current font. startX is
  //! relative to the current line's own start (post-indentation), matching
  //! how GetLineWidth()/Recalculate() already track their x accumulators.
  wxCoord NextTabStop(wxCoord startX) const;

  //! Width, in pixels, of `text` when it starts at horizontal position startX
  //! on its line -- like GetTextSize(text).GetWidth(), but correct when text
  //! contains embedded '\t' characters, whose width depends on where they
  //! fall (tab width is position-dependent, the one thing GetTextExtent()
  //! cannot compute on its own). For call sites that measure a raw m_text
  //! substring rather than a single already-tokenized StyledText snippet
  //! (a tab is always its own isolated snippet -- see MaximaTokenizer -- but
  //! can still be embedded anywhere inside an arbitrary m_text substring).
  wxCoord MeasureTextWidth(wxCoord startX, const wxString &text) const;

  /*! Is this character a strong right-to-left one (Hebrew, Arabic, Farsi, ...)?

    "Strong" in the sense of the Unicode bidirectional algorithm: a character
    that carries a direction of its own, as opposed to a digit, a space or
    punctuation, which take theirs from what surrounds them.
   */
  static bool IsStrongRightToLeft(wxUniChar c);

  //! Is this character a strong left-to-right one?
  static bool IsStrongLeftToRight(wxUniChar c);

  /*! Is the given display line written right to left?

    Decided by its first strong character, which is the rule the Unicode
    bidirectional algorithm uses for a paragraph. A line with no strong
    character at all - digits and punctuation only - follows the document.

    Only lines that are wholly one direction are handled here: for those the
    caret's position is the mirror of what measuring the text before it gives,
    which is exact and needs no reordering. A line that genuinely mixes the two
    scripts needs the real bidirectional algorithm instead - see
    GetLineBidiRuns() and Bidi::GetRuns(), which PositionToPoint(),
    MarkSelection() and HandleSpecialKey()'s arrow-key handling all fall back
    to for a line this function can't characterise with one direction.
   */
  bool LineIsRightToLeft(size_t line);

  //! Does this line mix strong characters of both directions?
  bool LineIsMixedDirection(size_t line);

  //! How many characters the given display line holds, excluding its newline.
  size_t LineLength(size_t line);

  /*! The left edge of the rectangle covering the run [runStart, runEnd).

    \param runStartX where PositionToPoint() puts runStart
    \param runWidth  how wide the run's text is

    The same for both directions except in one respect: on a right-to-left line
    the run is drawn from its end leftwards, so runStart marks its *right* edge
    and the rectangle begins a run-width further left.
   */
  wxCoord SelectionRunLeft(size_t runStart, size_t runEnd, wxCoord runStartX,
                           wxCoord runWidth);

  /*! Bidi::GetRuns() for a display line, as absolute positions into m_text
    (Bidi::GetRuns() itself only knows the line's own text, so its runs are
    relative to that - this is the shared first step for anything that needs
    the real bidi run structure at a whole-cell position instead).

    \return false (leaving *runs untouched) if libfribidi isn't compiled in
            (Bidi::IsAvailable()) or the line is empty.
   */
  bool GetLineBidiRuns(size_t line, std::vector<BidiRun> *runs);

  /*! Where position sits, in pixels from the line's own left edge, found via
    the real bidi algorithm (GetLineBidiRuns()) instead of assuming the whole
    line is one direction the way PositionToPoint()'s own fast path does.

    \return false (leaving *offset untouched) if libfribidi isn't compiled in.
   */
  bool MixedDirectionOffset(size_t line, size_t position, wxCoord *offset);

  /*! How far each line has to move right to sit flush with the widest one.

    Empty unless the document reads right-to-left
    (Configuration::RightToLeftDocument), so a left-to-right document pays
    nothing for this. Indexed by line; see RightToLeftLineOffset().
   */
  std::vector<wxCoord> RightToLeftLineOffsets();

  //! RightToLeftLineOffsets() for a single line, 0 when there is none.
  wxCoord RightToLeftLineOffset(size_t line);

  /*! true, if this cell's width has to be recalculated.
   */
  bool IsDirty() const override
    {
      return m_isDirty;
    }

  //! Toggles the visibility of the cursor which is used to make it blink.
  void SwitchCaretDisplay() override
    {
      m_displayCaret = !m_displayCaret;
    }

  void SetFocus(bool focus) override
    {
      m_hasFocus = focus;
    }

  bool IsActive() const override;

  //! Is the cursor at the start of this cell?
  bool CaretAtStart() const
    { return CursorPosition() == 0; }

  //! Move the cursor to the start of this cell
  void CaretToStart();

  //! Is the cursor at the end of this cell?
  bool CaretAtEnd() const
    { return CursorPosition() == m_text.Length(); }

  //! Move the cursor to the end of this cell
  void CaretToEnd();

  //! Move the cursor to a certain position in the cell
  void CaretToPosition(size_t pos);

  //! True, if there is undo information for this cell
  bool CanUndo() const {return m_history.CanUndo();}

  //! Issue an undo command
  void Undo();

  //! True, if a redo can be done for this cell.
  bool CanRedo() const {return m_history.CanRedo();}

  //! Issu a redo command
  void Redo();

    //! The undo history of this cell
  class History
  {
  public:
    enum Action : uintptr_t {
      any = 0,
      removeChar  = 1,
      addChar = 2
    };

    //! How an entry to the undo history looks like
    class HistoryEntry // 64 bytes
    {
    public:
      HistoryEntry(){};
      HistoryEntry(const wxString &text, long long selStart, long long selEnd) :
        m_text(text), m_selStart(selStart), m_selEnd(selEnd) {}
      long long SelectionStart() const {return m_selStart;}
      long long SelectionEnd() const {return m_selEnd;}
      wxString GetText() const {return m_text;}
    private:
      wxString m_text;
      long long m_selStart = -1;
      long long m_selEnd = -1;
    };
    bool AddState(HistoryEntry entry, Action action = any);
    bool AddState(const wxString &text, long long selStart, long long selEnd, Action action = any);
    bool Undo();
    bool Redo();
    bool CanUndo() const;
    bool CanRedo() const;
    void ClearUndoBuffer();
    HistoryEntry GetState() const;
  private:
    std::vector<HistoryEntry> m_history;
    //! Where in the undo history are we?
    size_t m_historyPosition = 0;
    Action m_lastAction = any;
  };

  //! Save the current contents of this cell in the undo buffer.
  void SaveValue(History::Action action = History::Action::any);

  /*! DivideAtCaret
    Returns the string from caret to end and
    modifies the m_text so it contains only the string
    from beginning to caret
    Used for 'Divide Cell', called from MathCtrl
  */
  wxString DivideAtCaret();

  void CommentSelection();

  //! Query if this cell needs to be re-evaluated by maxima
  bool ContainsChanges() const
    { return m_containsChanges; }

  //! Set the information if this cell needs to be re-evaluated by maxima
  void ContainsChanges(bool changes)
    { m_containsChanges = m_containsChangesCheck = changes; }

  bool CheckChanges();

  /*! Replaces all occurrences of a given string
   */
  size_t ReplaceAll(wxString oldString, const wxString &newString, bool ignoreCase);
  size_t ReplaceAll_RegEx(const wxString &oldString, const wxString &newString);

  /*! Finds the next occurrences of a string

    \param str The string to search for
    \param down
    - true: search downwards
    - false: search upwards
    \param ignoreCase
    - true: Case-insensitive search
    - false: Case-sensitive search
  */
  bool FindNext(wxString str, const bool &down, const bool &ignoreCase);
  bool FindNext_RegEx(wxString str, const bool &down);

  bool IsSelectionChanged() const { return m_selectionChanged; }

  void GetSelection(size_t *start, size_t *end) const
    {
      *start = SelectionStart();
      *end   = SelectionEnd();
    }

  /*! Replace the current selection with a string

    \param oldStr The old string in the selection. If this string doesn't match
    the selection this function doesn't replace it.
    \param newString The new string oldStr has to be replaced by
    \param keepSelected
    - true = we want the new string to be selected afterwards
    - false = the selection is cleared after replacing the string
    and moving the cursor to its end.
    \param ignoreCase true = ignore the case of the string to replace
    \param replaceMaximaString true = replace strings including the double quotes.
  */
  bool ReplaceSelection(const wxString &oldStr, const wxString &newString,
                        bool keepSelected = false, bool ignoreCase = false,
                        bool replaceMaximaString = false);
  bool ReplaceSelection_RegEx(const wxString &oldStr, const wxString &newString);

  //! Convert the current selection to a string
  wxString GetSelectionString() const;

  //! Try to determine the selection's text style
  TextStyle GetSelectionStyle() const;

  //! The word the cursor currently is at
  wxString GetWordUnderCaret();

  //! Get the command the cursor is in the arguments for.
  wxString GetCurrentCommand() const;

  //! Sets the index the error is at
  void SetErrorIndex(size_t index){m_errorIndex = index; m_errorIndexSet = true;}
  //! Clears the index the error is at
  void ClearErrorIndex(){m_errorIndexSet = false;}

  bool ErrorIndexSet() const {return m_errorIndexSet;}

  void GotoError(){SetCaretPosition(m_errorIndex); ActivateCursor(); ClearErrorIndex();}

  //! Start a new line and optionally auto-indent it.
  void ProcessNewline(bool keepCursorAtStartOfLine = true);

  //! Get the cursor's current position inside the cell.
  size_t GetCaretPosition() const
    { return CursorPosition(); }

  //! Convert a number to unicode chars.
  void ConvertNumToUNicodeChar();

  //! Set the cursor's current position inside the cell.
  void SetCaretPosition(size_t pos){CursorPosition(pos);}

  bool FindNextTemplate(bool left = false);

  void InsertText(wxString text);

  wxString TextInFrontOfSelection() const
    {
      return GetValue().Left(SelectionLeft());
    }

  //! Return to the selection after the cell has been left upwards
  void ReturnToSelectionFromTop()
    {
      SetSelection(m_lastSelectionStart, 0);
    }

  void SetType(CellType type) override;
  void SetStyle(TextStyle style) override;

  bool NeedsRecalculation(AFontSize fontSize) const override;

  //! Return to the selection after the cell has been left downwards
  void ReturnToSelectionFromBot()
    {
      SetSelection(m_lastSelectionStart, m_text.Length());
    }

  //! Get the list of commands, parenthesis, strings and whitespaces in a code cell
//  const MaximaTokenizer::TokenList &GetDisplayedTokens() const;
  //! Get the list of commands, parenthesis, strings and whitespaces including hidden ones
  const MaximaTokenizer::TokenList &GetAllTokens() const;

private:
  //! Used by StyleTextTexts(): pushes `line` as one or more StyledText
  //! snippets, isolating each '\t' into its own snippet the way the code-cell
  //! tokenizer already guarantees -- so Draw()/Recalculate()/GetLineWidth()
  //! can expand it to a tab stop instead of measuring it as an ordinary (and,
  //! for a tab, meaningless) glyph. Only the first resulting snippet carries
  //! indentChar, since Draw() draws it at a fixed position regardless of
  //! which snippet triggers it.
  void PushTextLine(const wxString &line, const wxString &indentChar) const;

  //! Clamps a (possibly negative or past-the-end) position to a valid index into
  //! the current text, i.e. to [0, m_text.Length()]. Used by the selection and
  //! cursor setters so the stored positions are always valid.
  size_t ClampToText(long long pos) const {
    if (pos < 0)
      return 0;
    return std::min(static_cast<size_t>(pos), m_text.Length());
  }
  size_t m_selectionStart = 0;
  size_t m_selectionEnd = 0;
  size_t m_lastSelectionStart = 0;
protected:
  /*! A piece of styled text for syntax highlighting

    A piece of styled text may be
    - a text line
    - a command, parenthesis, number, line ending
    - '\n'
    - whitespace
    - '\\r' indicating a soft line break optionally equipped with indentation
    and a character that marks a continued quote or similar

    Protected (not private): CodeEditorCell::ToRTF() serializes
    \ref m_styledText snippet-by-snippet to keep the syntax highlighting.
  */
  class StyledText
  {
  private:
    /*! The text of this text portion
     */
    wxString m_text;
    //! The color of this text portion
    TextStyle m_style = TS_CODE_DEFAULT;
    //! Chars that mark continued indentation
    wxString m_indentChar;
    //! The cached width of this piece of text
    mutable CachedInteger<wxCoord> m_width;
    //! By How many pixels we want to indent this line?
    wxCoord m_indentPixels = 0;
    //! Do we really want to style this text portion different than the default?
    bool m_styleThisText = false;
  public:
    //! Defines a piece of styled text
    StyledText(TextStyle style, const wxString &text)
      : m_text(text), m_style(style), m_styleThisText(true) {}

    //! Defines a piece of text with the default style that possibly is indented
    explicit StyledText(const wxString &text, TextStyle style, wxCoord indentPixels = 0,
                        const wxString &indentChar = {})
      : m_text(text), m_style(style), m_indentChar(indentChar), m_indentPixels(indentPixels)  {}

    void SetWidth(wxCoord width){m_width = width;}
    void ResetSize(){m_width.Invalidate();}
    wxCoord GetWidth() const {return m_width;}
    bool SizeKnown() const {return m_width.IsValid();}
    //! Returns the piece of text
    const wxString &GetText() const { return m_text; }
    //! Changes the piece of text kept in this token
    void SetText(const wxString &text) { m_text = text; }
    //! Changes the indentation level of this token
    void SetIndentation(wxCoord indentPixels, const wxString &indentString = {})
      {
        m_indentPixels = indentPixels;
        m_indentChar = indentString;
      }
    //! By how many pixels do we need to indent this line due to a bullet list or similar?
    wxCoord GetIndentPixels() const { return m_indentPixels; }
    const wxString &GetIndentChar() const { return m_indentChar; }
    //! If IsStyleSet() is true this function returns the style of this text
    //! portion
    TextStyle GetTextStyle() const { return m_style; }
    // Has an individual text style been set for this text portion?
    bool IsStyleSet() const { return m_styleThisText; }
  };

private:
#if defined __WXOSX__
  bool HandleCtrlCommand(wxKeyEvent &ev);
#endif
  bool HandleSpecialKey(wxKeyEvent &event);
  bool HandleOrdinaryKey(wxKeyEvent &event);

  void FontsChanged() const override
    {
      m_widths.clear();
    }

  /*! Adds soft line breaks to code cells, if needed.

    \todo: We could do an incremental indentation calculation that starts at the last word:
    The current behavior is O(n^2) (scanning the text needs linear time and for each word
    the indentation algorithm scans the text again) which is unfortunate.
  */
  /*! Where StyleTextCode() may place the next soft break.

    A break can happen at a plain space (the space's render snippet is turned
    into a "\r" marker in place) or right after certain operators (a zero-width
    "\r" marker is inserted). \c snippetIdx indexes \ref m_styledText: the space
    snippet to replace, or the position to insert the operator marker at.
    \c textPos is the content offset where the continuation line begins (the
    value pushed to \ref m_softBreaks).
  */
  struct SoftBreakCandidate {
    std::size_t snippetIdx = 0;
    std::size_t textPos = 0;
    bool insert = false; //!< false: replace a space snippet; true: insert after an operator
    bool valid = false;
  };

  void HandleSoftLineBreaks_Code(SoftBreakCandidate &candidate, wxCoord &lineWidth,
                                 const wxString &token, wxCoord &indentationPixels) const;

  /*! May a soft break be placed right after this operator token?

    Breaks are offered after the arithmetic operators + - * / and after ( ) ; $,
    but never in the middle of a "**" power operator (the tokenizer emits each
    '*' as its own token). \p prevToken / \p nextToken are the neighbouring
    token texts (empty if none) so the "**" case can be excluded.
  */
  static bool BreakAfterOperator(const wxString &token, const wxString &prevToken,
                                 const wxString &nextToken);

  /*! Is there a soft (word-wrap) line break immediately before this offset?

    Consults the \ref m_softBreaks side table. Used by the offset→display-line
    walkers so a soft break behaves like a zero-width newline without a marker
    character having to sit inside \ref m_text.
  */
  bool IsSoftBreakBefore(std::size_t pos) const {
    return std::binary_search(m_softBreaks.begin(), m_softBreaks.end(), pos);
  }

  /*! How many chars do we need to indent text at the position the caret is currently at?

    \todo We should provide an alternative function that allows to resume the calculation
    for the next word/line - which would provide an additional speedup.
  */
  size_t GetIndentDepth(wxString text, size_t  positionOfCaret) const;

  /*! Handle ESC shortcuts for special characters

    These characters can be thought to LaTeX and the html browser if necessary in
    TextCell::ToTeX and EditorCell::ToTeX. They can also be
    converted to maxima strings in MaximaEvaluator::SendMaxima.
  */
  static wxString InterpretEscapeString(const wxString &txt);

  //! Draw a box that marks the current selection
  void MarkSelection(wxDC *dc, size_t start, size_t end, TextStyle style);

  //! Determines the size of a text snippet
  wxSize GetTextSize(const wxString &text) const;

  //! The memory for the undo history
  History m_history;  
  //! Set the editor's state from a history entry
  void SetState(const History::HistoryEntry &state);
  //! Get the styled text
  std::vector<StyledText> &GetStyledText();

//** Large fields
//**
  typedef std::unordered_map <wxString, wxSize, wxStringHash> StringHash;
  //! Cached widths of text snippets, one width per style
  mutable StringHash m_widths;

  //! A list of all potential autoComplete targets within this cell
  mutable std::vector<wxString> m_wordList;

  //! The individual commands, parenthesis, strings and whitespaces a code cell consists of
  mutable MaximaTokenizer::TokenList m_tokens;
  //! The individual commands, parenthesis, strings and whitespaces including hidden lines
  mutable MaximaTokenizer::TokenList m_tokens_including_hidden;

  /*! The text this Editor contains
   */
  mutable wxString m_text;
protected:
  //! The styled-run rendering representation of m_text; see StyledText.
  mutable std::vector<StyledText> m_styledText;
private:

  /*! Soft (word-wrap) line-break positions, kept OUTSIDE the content string.

    A soft break is derived layout data, not user content, so it must never
    live inside \ref m_text (writing a '\r' marker into the content string was
    the historical root of a whole bug family: the marker leaked into
    copy/paste, XML, ToString and the string sent to Maxima, and had to be
    scrubbed back to a space in dozens of places).

    Each entry is a *zero-width insertion offset* into \ref m_text: "a display
    line ends just before this offset". Offsets are strictly increasing.
    \ref StyleText() clears and re-derives the whole vector on every restyle,
    so wrapping stays a pure function of (content, width). The matching render
    markers still live in \ref m_styledText as "\r" snippets (that list is a
    throw-away rendering representation, so it may carry layout markers).
  */
  mutable std::vector<std::size_t> m_softBreaks;

  //! See SetDiffHighlights(). Empty for every cell outside the diff viewer.
  std::vector<std::pair<std::size_t, std::size_t>> m_diffHighlights;
  //! m_text's length when m_diffHighlights was set: the ranges only stay valid
  //! while no edit happened, and every insertion/deletion changes the length
  //! (soft-wrapping doesn't - it swaps ' ' and '\r' in place).
  std::size_t m_diffHighlightsTextLength = 0;

//** 8/4 bytes
//**
  DocumentCellPointers *const m_documentCellPointers = GetDocumentCellPointers();
  ViewCellPointers *const m_viewCellPointers = GetViewCellPointers();

//** 4 bytes
//**
  size_t m_errorIndex = 1;
  mutable size_t m_numberOfLines = 1;
  mutable wxCoord m_charHeight = 12;
  long m_paren1 = -1, m_paren2 = -1;

//** 2 bytes
//**
  AFontStyle m_fontStyle;
  AFontWeight m_fontWeight;

//** Bitfield objects (2 bytes)
//**
  //! Mark this cell as "Automatically answer questions".
  bool m_autoAnswer : 1 = false;
  //! true, if this function has changed since the last evaluation by maxima
  mutable bool m_containsChanges : 1 = false;
  bool m_containsChangesCheck : 1 = false;
  bool m_displayCaret : 1 = false;
  bool m_hasFocus : 1 = false;
  bool m_errorIndexSet : 1 = false;
  //! Has the selection changed since the last draw event?
  bool m_selectionChanged : 1 = false;
  //! Does this cell's size have to be recalculated?
  bool m_underlined : 1 = false;
  //! Does the list of tokens including hidden items need to be recalculated?
  mutable bool m_tokens_including_hidden_valid : 1 = false;
  //! Does the list of displayed tokens need to be recalculated?
  mutable bool m_tokens_valid : 1 = false;
  mutable bool m_isDirty : 1 = true;
};

#endif // EDITORCELL_H
