buffer.hpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. #pragma once
  2. #include <string>
  3. #include <vector>
  4. #include <functional>
  5. #include <optional>
  6. #include <filesystem>
  7. #include <memory>
  8. #include "lumacs/theme.hpp" // For ThemeElement mapping if needed
  9. namespace lumacs {
  10. /// @brief Represents a cursor position in a buffer (line, column).
  11. struct Position {
  12. size_t line = 0;
  13. size_t column = 0;
  14. auto operator<=>(const Position&) const = default;
  15. };
  16. /// @brief Represents a range of text in the buffer [start, end).
  17. struct Range {
  18. Position start;
  19. Position end;
  20. auto operator<=>(const Range&) const = default;
  21. };
  22. /// @brief Buffer lifecycle and modification events.
  23. enum class BufferEvent {
  24. // Lifecycle events
  25. Created, ///< Buffer was created.
  26. Loaded, ///< File was loaded into buffer.
  27. Closed, ///< Buffer is being closed.
  28. // Modification events
  29. BeforeChange, ///< About to modify buffer (can be used to save state for undo).
  30. AfterChange, ///< Buffer content was modified.
  31. LineChanged, ///< Specific line was modified.
  32. // File operations
  33. BeforeSave, ///< About to save to disk.
  34. AfterSave, ///< File was saved to disk.
  35. // Language/mode
  36. LanguageChanged ///< Buffer language/mode changed (e.g., for syntax highlighting).
  37. };
  38. /// @brief Data associated with a buffer event.
  39. struct BufferEventData {
  40. BufferEvent event;
  41. size_t line = 0; ///< Line number for LineChanged events.
  42. std::string language = ""; ///< For LanguageChanged events.
  43. };
  44. /// @brief Text styling attributes for syntax highlighting.
  45. struct TextAttribute {
  46. enum class Style {
  47. Normal = 0,
  48. Bold = 1,
  49. Italic = 2,
  50. Underline = 4,
  51. };
  52. // Common semantic colors for syntax highlighting (Legacy, used for mapping)
  53. enum class ColorType {
  54. Default,
  55. Keyword, // if, for, while, etc.
  56. String, // String literals
  57. Comment, // Comments
  58. Function, // Function names
  59. Type, // Type names, classes
  60. Number, // Numeric literals
  61. Operator, // +, -, *, etc.
  62. Variable, // Variable names
  63. Constant, // Constants, enums
  64. Error, // Error highlighting
  65. };
  66. std::string face_name = "default";
  67. TextAttribute() = default;
  68. TextAttribute(std::string face) : face_name(std::move(face)) {}
  69. // Legacy constructor
  70. TextAttribute(ColorType c, int /*s*/ = 0) {
  71. // Map legacy ColorType to face name
  72. switch (c) {
  73. case ColorType::Keyword: face_name = "font-lock-keyword-face"; break;
  74. case ColorType::String: face_name = "font-lock-string-face"; break;
  75. case ColorType::Comment: face_name = "font-lock-comment-face"; break;
  76. case ColorType::Function: face_name = "font-lock-function-name-face"; break;
  77. case ColorType::Type: face_name = "font-lock-type-face"; break;
  78. case ColorType::Number: face_name = "font-lock-constant-face"; break; // Map number to constant
  79. case ColorType::Operator: face_name = "default"; break; // No specific face yet
  80. case ColorType::Variable: face_name = "font-lock-variable-name-face"; break;
  81. case ColorType::Constant: face_name = "font-lock-constant-face"; break;
  82. case ColorType::Error: face_name = "error"; break;
  83. default: face_name = "default"; break;
  84. }
  85. // Note: style flags (s) are ignored here as faces define weight/slant
  86. }
  87. bool operator==(const TextAttribute&) const = default;
  88. };
  89. /// @brief A text range associated with a specific style attribute.
  90. struct StyledRange {
  91. Range range;
  92. TextAttribute attr;
  93. StyledRange() = default;
  94. StyledRange(Range r, TextAttribute a) : range(r), attr(a) {}
  95. bool operator==(const StyledRange&) const = default;
  96. };
  97. /// @brief Snapshot of buffer state for Undo/Redo operations.
  98. struct UndoState {
  99. std::vector<std::string> lines;
  100. Position cursor;
  101. UndoState() = default;
  102. UndoState(const std::vector<std::string>& l, Position c) : lines(l), cursor(c) {}
  103. };
  104. /// @brief A text buffer that manages the content of a file or scratch buffer.
  105. ///
  106. /// The Buffer class is the core data structure for text storage. It manages:
  107. /// - Text content (lines)
  108. /// - File association (save/load)
  109. /// - Modification tracking
  110. /// - Syntax highlighting (styling)
  111. /// - Undo/Redo history
  112. /// - Mark and Region state
  113. class Buffer {
  114. public:
  115. /// @brief Create an empty buffer.
  116. Buffer();
  117. /// @brief Create a named buffer (e.g., for scratch).
  118. explicit Buffer(std::string name);
  119. /// @brief Create a buffer loaded from a file.
  120. /// @param path The path to the file.
  121. /// @return Optional Buffer (empty if load failed).
  122. static std::optional<Buffer> from_file(const std::filesystem::path& path);
  123. // Disable copy, allow move
  124. Buffer(const Buffer&) = delete;
  125. Buffer& operator=(const Buffer&) = delete;
  126. Buffer(Buffer&&) noexcept = default;
  127. Buffer& operator=(Buffer&&) noexcept = default;
  128. ~Buffer() = default;
  129. // === Content Access ===
  130. /// @brief Get the number of lines in the buffer.
  131. [[nodiscard]] size_t line_count() const noexcept;
  132. /// @brief Get a specific line by index (0-based).
  133. [[nodiscard]] const std::string& line(size_t index) const;
  134. /// @brief Get all lines in the buffer.
  135. [[nodiscard]] const std::vector<std::string>& lines() const noexcept;
  136. /// @brief Get the entire buffer content as a single string (lines joined by newlines).
  137. [[nodiscard]] std::string content() const;
  138. // === Modification ===
  139. /// @brief Insert text at the specified position.
  140. void insert(Position pos, std::string_view text);
  141. /// @brief Insert a single character at the specified position.
  142. void insert_char(Position pos, char c);
  143. /// @brief Insert a newline at the specified position (splits the line).
  144. void insert_newline(Position pos);
  145. /// @brief Delete the text within the specified range.
  146. void erase(Range range);
  147. /// @brief Delete the character *before* the specified position (Backspace behavior).
  148. void erase_char(Position pos);
  149. /// @brief Replace the text in the specified range with new text.
  150. void replace(Range range, std::string_view text);
  151. /// @brief Find the next occurrence of a query string starting from start_pos.
  152. [[nodiscard]] std::optional<Range> find(const std::string& query, Position start_pos) const;
  153. /// @brief Find the previous occurrence of a query string starting backwards from start_pos.
  154. [[nodiscard]] std::optional<Range> find_backward(const std::string& query, Position start_pos) const;
  155. /// @brief Clear the entire buffer content.
  156. void clear();
  157. // === File Operations ===
  158. /// @brief Save the buffer content to its associated file.
  159. bool save();
  160. /// @brief Save the buffer content to a new file path and update association.
  161. bool save_as(const std::filesystem::path& path);
  162. /// @brief Check if the buffer has been modified since the last save.
  163. [[nodiscard]] bool is_modified() const noexcept;
  164. /// @brief Get the file path associated with this buffer (if any).
  165. [[nodiscard]] std::optional<std::filesystem::path> file_path() const noexcept;
  166. /// @brief Set the file path associated with this buffer.
  167. void set_file_path(const std::filesystem::path& path);
  168. // === Buffer Properties ===
  169. /// @brief Get the buffer name.
  170. [[nodiscard]] const std::string& name() const noexcept;
  171. /// @brief Set the buffer name.
  172. void set_name(std::string name);
  173. /// @brief Check if a position is valid (within buffer bounds).
  174. [[nodiscard]] bool is_valid_position(Position pos) const noexcept;
  175. /// @brief Clamp a position to valid buffer bounds.
  176. [[nodiscard]] Position clamp_position(Position pos) const noexcept;
  177. // === Syntax Highlighting / Styling ===
  178. /// @brief Apply a style attribute to a specific range of text.
  179. void set_style(Range range, TextAttribute attr);
  180. /// @brief Get all styled ranges for a specific line.
  181. [[nodiscard]] const std::vector<StyledRange>& get_line_styles(size_t line) const;
  182. /// @brief Clear all styling information in the buffer.
  183. void clear_styles();
  184. /// @brief Clear styling information for a specific line.
  185. void clear_line_styles(size_t line);
  186. // === Events & Hooks ===
  187. using BufferEventCallback = std::function<void(const BufferEventData&)>;
  188. using BufferEventCallbackHandle = size_t;
  189. /// @brief Register a callback for buffer events.
  190. BufferEventCallbackHandle on_buffer_event(BufferEventCallback callback);
  191. /// @brief Disconnect a previously registered buffer event callback.
  192. void disconnect_event_callback(BufferEventCallbackHandle handle);
  193. /// @brief Get the language/mode identifier (e.g., "cpp", "lua").
  194. [[nodiscard]] const std::string& language() const noexcept { return language_; }
  195. /// @brief Set the language/mode identifier (triggers LanguageChanged event).
  196. void set_language(std::string lang);
  197. /// @brief Auto-detect language from file extension.
  198. static std::string detect_language(const std::filesystem::path& path);
  199. // === Undo/Redo ===
  200. /// @brief Undo the last operation.
  201. /// @param out_cursor Output parameter to restore cursor position.
  202. /// @return true if undo was successful.
  203. bool undo(Position& out_cursor);
  204. /// @brief Redo the last undone operation.
  205. /// @param out_cursor Output parameter to restore cursor position.
  206. /// @return true if redo was successful.
  207. bool redo(Position& out_cursor);
  208. /// @brief Check if undo is possible.
  209. [[nodiscard]] bool can_undo() const noexcept { return !undo_stack_.empty(); }
  210. /// @brief Check if redo is possible.
  211. [[nodiscard]] bool can_redo() const noexcept { return !redo_stack_.empty(); }
  212. /// @brief Save current state to undo stack (internal use).
  213. void save_undo_state(Position cursor);
  214. /// @brief Clear redo stack (internal use).
  215. void clear_redo_stack();
  216. // === Mark and Region ===
  217. /// @brief Set the mark at the specified position and activate it.
  218. void set_mark(Position pos);
  219. /// @brief Deactivate the mark (hide region highlight).
  220. void deactivate_mark();
  221. /// @brief Get the current mark position (if set).
  222. [[nodiscard]] std::optional<Position> mark() const noexcept { return mark_; }
  223. /// @brief Check if the mark is currently active.
  224. [[nodiscard]] bool has_active_mark() const noexcept { return mark_active_; }
  225. /// @brief Get the region between the mark (if active) and the given point.
  226. [[nodiscard]] std::optional<Range> get_region(Position point) const;
  227. /// @brief Get the text content within the specified range.
  228. [[nodiscard]] std::string get_text_in_range(Range range) const;
  229. private:
  230. std::string name_;
  231. std::vector<std::string> lines_;
  232. std::optional<std::filesystem::path> file_path_;
  233. bool modified_;
  234. std::string language_;
  235. // Styling information: one vector of styled ranges per line
  236. std::vector<std::vector<StyledRange>> line_styles_;
  237. // Event callbacks
  238. std::map<BufferEventCallbackHandle, BufferEventCallback> event_callbacks_;
  239. size_t next_callback_handle_ = 0;
  240. // Undo/Redo stacks
  241. std::vector<UndoState> undo_stack_;
  242. std::vector<UndoState> redo_stack_;
  243. static constexpr size_t MAX_UNDO_LEVELS = 100;
  244. bool in_undo_redo_ = false; // Prevent saving state during undo/redo
  245. // Mark and Region
  246. std::optional<Position> mark_;
  247. bool mark_active_ = false;
  248. /// Ensure the buffer has at least one line
  249. void ensure_min_lines();
  250. /// Mark the buffer as modified
  251. void mark_modified();
  252. /// Ensure styles vector matches lines vector size
  253. void ensure_styles_size();
  254. /// Emit a buffer event to all registered callbacks
  255. void emit_event(BufferEvent event, size_t line = 0);
  256. };
  257. } // namespace lumacs