library/forms/mforms/code_editor.h (302 lines of code) (raw):

/* * Copyright (c) 2010, 2019, Oracle and/or its affiliates. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2.0, * as published by the Free Software Foundation. * * This program is designed to work with certain software (including * but not limited to OpenSSL) that is licensed under separate terms, as * designated in a particular file or component or in included license * documentation. The authors of MySQL hereby grant you an additional * permission to link the program and your derivative works with the * separately licensed software that they have either included with * the program or referenced in the documentation. * 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, version 2.0, 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 St, Fifth Floor, Boston, MA 02110-1301 USA */ #pragma once #include <libxml/tree.h> #include "Scintilla.h" #include "base/notifications.h" #include "mforms/view.h" #include "mforms/utilities.h" /** * Provides a code editor with syntax highlighting for mforms. */ class TiXmlDocument; class TiXmlElement; namespace mforms { class CodeEditor; class Menu; class FindPanel; enum SyntaxHighlighterLanguage { LanguageNone, LanguageMySQL56, LanguageMySQL57, LanguageMySQL80, LanguageHtml, // includes embedded xml, javascript, php, vb, python LanguagePython, LanguageCpp, // Lexer for C++, C, Java, and JavaScript (which includes JSON). LanguageJS, LanguageJson, LanguageMySQL = LanguageMySQL80, // Always the latest (released) language. }; /** * A number of flags used to specify additional markup for a line (shown in the gutter). */ enum LineMarkup { LineMarkupNone = 0, // No markup for the given line. LineMarkupStatement = 1 << 0, // Marks a line as having a statement starting on it. LineMarkupError = 1 << 1, // Marks a syntax error in that line. LineMarkupBreakpoint = 1 << 2, // Line has a marker set for a break point. LineMarkupBreakpointHit = 1 << 3, // Line has a marker set for a break point which is currently hit. LineMarkupCurrent = 1 << 4, // Current execution line. LineMarkupErrorContinue = 1 << 5, // Marker for a failed sql statement (execution). LineMarkupAll = 0xFF, // All markup, useful for remove_markup. }; // A collection of markup, attached to the original line and going to be removed // or moved to a new line. typedef struct { int original_line; int new_line; LineMarkup markup; } LineMarkupChangeEntry; typedef std::vector<LineMarkupChangeEntry> LineMarkupChangeset; #ifndef SWIG inline LineMarkup operator|(LineMarkup a, LineMarkup b) { return (LineMarkup)((int)a | (int)b); } #endif // Indicators for a portion of text. Can span more than a single line or only part of a line. enum RangeIndicator { RangeIndicatorNone = 0, RangeIndicatorError = 1 << 0, // Red squiggles under a range of text. }; enum CodeEditorFeature { FeatureNone = 0, FeatureWrapText = 1 << 0, // Enables word wrapping. FeatureGutter = 1 << 1, // Show/Hide gutter. FeatureReadOnly = 1 << 2, FeatureShowSpecial = 1 << 3, // Show white spaces and line ends with special chars. FeatureUsePopup = 1 << 4, // Use built-in context menu. FeatureConvertEolOnPaste = 1 << 5, // Convert line endings to the current value in the editor // when pasting text. FeatureScrollOnResize = 1 << 6, // Scroll caret into view if it would be hidden by a resize action. FeatureFolding = 1 << 7, // Enable code folding. FeatureAutoIndent = 1 << 8, // Auto indent the new line on pressing enter. FeatureAll = 0xFFFF, }; #ifndef SWIG inline CodeEditorFeature operator|(CodeEditorFeature a, CodeEditorFeature b) { return (CodeEditorFeature)((int)a | (int)b); } #endif enum AutoCompletionEventType { AutoCompletionSelection, // The user selected an entry in the auto completion list. AutoCompletionCancelled, // Auto completion was cancelled. AutoCompletionCharDeleted, // A character was deleted while auto completion was active. }; enum EndOfLineMode { EolCRLF = 0, EolCR = 1, EolLF = 2, // Default }; enum FindFlags { FindDefault = 0, FindMatchCase = (1 << 0), FindWrapAround = (1 << 1), FindWholeWords = (1 << 2), FindRegex = (1 << 3) }; #ifndef SWIG inline FindFlags operator|(FindFlags a, FindFlags b) { return (FindFlags)((int)a | (int)b); } inline FindFlags& operator|=(FindFlags& a, FindFlags b) { a = (FindFlags)((int)a | (int)b); return a; } #endif #ifndef SWIG /** * Helper class to manage editor configuration files. */ class MFORMS_EXPORT CodeEditorConfig { private: std::vector<std::string> _languages; SyntaxHighlighterLanguage _used_language; std::map<std::string, std::string> _keywords; std::map<std::string, std::string> _properties; std::map<std::string, std::string> _settings; std::map<int, std::map<std::string, std::string> > _styles; xmlDocPtr _xmlDocument; xmlNodePtr _xmlLanguageElement; protected: void parse_properties(); void parse_settings(); void parse_keywords(); void parse_styles(); public: CodeEditorConfig(SyntaxHighlighterLanguage language); ~CodeEditorConfig(); std::vector<std::string> get_languages() { return _languages; }; // TODO: add setters when customization is required. std::map<std::string, std::string> get_keywords() { return _keywords; }; std::map<std::string, std::string> get_properties() { return _properties; }; std::map<std::string, std::string> get_settings() { return _settings; }; std::map<int, std::map<std::string, std::string> > get_styles() { return _styles; }; }; #endif // !SWIG #ifndef DOXYGEN_SHOULD_SKIP_THIS #ifndef SWIG struct CodeEditorImplPtrs { bool (*create)(CodeEditor* self, bool showInfo); sptr_t (*send_editor)(CodeEditor* self, unsigned int message, uptr_t wParam, sptr_t lParam); void (*set_status_text)(CodeEditor* self, const std::string& text); }; struct MarginSizes { sptr_t margin1; sptr_t margin2; sptr_t margin3; sptr_t margin4; }; #endif #endif class MFORMS_EXPORT CodeEditor : public View, public base::Observer { public: enum EditorMargin { LineNumberMargin, MarkersMargin, FolderMargin, TextMargin }; CodeEditor(void* host = NULL, bool showInfo = true); ~CodeEditor(); /** Set editor colors based on the current OS appearance. */ void updateColors(); /** Set custom color and size of editor margins. */ void setWidth(EditorMargin margin, int size, const std::string& adjustText = ""); void setColor(EditorMargin margin, base::Color color, bool foreground = false); void showMargin(EditorMargin, bool show = true); void setMarginText(const std::string& str); void setMarginText(const std::string& str, size_t line); void setScrollWidth(size_t width); int getLineHeight(int line); /** Replaces the text in the editor. */ void set_text(const char* text); void set_value(const std::string& text); /** Replaces the text in the editor but preserves top line, caret position and selection. * This might not always work, especially when replacing large text by small text. */ void set_text_keeping_state(const char* text); /** Appends the given number of chars to end of the document. Allows to add nulls too. * The length is (as always) a byte count. */ void append_text(const char* text, size_t length); /** Replaces the selected text in the editor by the new text. If no text is selected then * the new text is inserted at the caret position. */ void replace_selected_text(const std::string& text); /** Returns a copy of the text which is currently in the editor. If selection_only is true only * the current selection is returned. If there is no selection then the result is an empty * string in that case. */ const std::string get_text(bool selection_only); virtual std::string get_string_value() override { return get_text(false); } /** Returns the text in the given range (inclusive endpoints), regardless of the selection state. * The range is automatically adjusted if it lies outside the available total text range. */ const std::string get_text_in_range(size_t start, size_t end); /** Returns a direct pointer to the text in the editor control (no copying takes place) and its length * in bytes. The text can contain embedded nulls and should therefore not be handled like * a null terminated string (even though it is actually null terminated). * The call takes care to make the text a continuous block of characters terminated with an additional null. * The returned pointer is valid until the next change in the editor happens, so use it only * for short term tasks (e.g. error parsing, direct search etc.). * Don't change the text in any way or the editor might get out of sync. */ std::pair<const char*, size_t> get_text_ptr(); /** Selects the text at the given range. If length is 0 it will just move the caret. * NOTE: Scintilla uses bytes everywhere when a position or length is set or read. So be very * careful when doing char maths (we use utf-8 with a variable code length per character). */ void set_selection(size_t start, size_t length); /** Gets the current selection range. */ void get_selection(size_t& start, size_t& length); /** Removes the current selection without moving the caret. */ void clear_selection(); /** Gets the byte range for the given line. Returns false if the line number is invalid */ bool get_range_of_line(ssize_t line, ssize_t& start, ssize_t& end); /** Sets the language for the syntax highlighter. */ void set_language(SyntaxHighlighterLanguage language); /** Adds the given markup to a line if not yet there. Does not touch other markup. */ void show_markup(LineMarkup markup, size_t line); /** Removes the given markup from that line, without affecting other markup (except for LineMarkupAll). * If markup is LineMarkupAll then all markers are removed for the given line. * If line is < 0 then all marker are removed from all lines. * However, it is not possible to remove a specific marker from all lines. */ void remove_markup(LineMarkup markup, ssize_t line); /** * Determines if the given line contains the given markup. * Returns true if at least one of the given markup types was found. */ bool has_markup(LineMarkup markup, size_t line); /** Adds the given indicator styling to a range of characters. */ void show_indicator(RangeIndicator indicator, size_t start, size_t length); /** Returns the indicator styling at the given position (if any). */ RangeIndicator indicator_at(size_t position); /** Removes the given indicator styling from the given range. */ void remove_indicator(RangeIndicator indicator, size_t start, size_t length); /** Returns the number of lines currently in the editor. */ size_t line_count(); /** The total length of the text in the editor in bytes. */ size_t text_length(); /** Returns the character position of the given line. */ size_t position_from_line(size_t line_number); /** Returns the line number from the given character position. */ size_t line_from_position(size_t position); virtual void set_font(const std::string& fontDescription) override; // e.g. "Trebuchet MS bold 9" /** Enables or disables different features in the editor which have a yes/no behavior. */ void set_features(CodeEditorFeature features, bool flag); /** Toggles the given feature(s) to their opposite state. */ void toggle_features(CodeEditorFeature features); void set_read_only(bool flag); void reset_undo_stack(); /** Resets the editor's dirty state or queries it. */ void reset_dirty(); bool is_dirty(); /** Retrieves or sets the position of the caret in the editor, specified as byte position. */ size_t get_caret_pos(); void set_caret_pos(size_t position); /** Retrieves the line and column (both zero-based) for a given byte position. */ void get_line_column_pos(size_t position, size_t& line, size_t& column); /** Standard edit functions used from menus. */ bool can_undo(); void undo(); bool can_redo(); void redo(); bool can_cut(); void cut(); bool can_copy(); void copy(); bool can_paste(); void paste(); bool can_delete(); void do_delete(); void select_all(); /** Sets the given text in the status field of the editor. Not all platforms support this, though. */ void set_status_text(const std::string& text); // ----- Find and replace void show_find_panel(bool replace); void hide_find_panel(); FindPanel* get_find_panel() { return _find_panel; }; /** Used to set a callback which is called to set up the layout for the find panel and * shows/hides it as requested. This allows to decouple platform specific needs for embedding * the find panel in various parts of the application (even non-mforms). */ void set_show_find_panel_callback(std::function<void(CodeEditor*, bool)> callback); /** Searches for the given text according to the parameters and selects the first occurrence. * Returns true if something was found, false otherwise. */ bool find_and_highlight_text(const std::string& search_text, FindFlags flags, bool scroll_to, bool backwards); /** Searches for the given text according to the parameters and replaces it by the text. * Returns the number of replaced text occurrences. */ size_t find_and_replace_text(const std::string& search_text, const std::string& new_text, FindFlags flags, bool do_all); /** Searches for the next placeholder char combination and selects it when found. The text is * also scrolled into view. */ void jump_to_next_placeholder(); //----- Auto completion ----- /** Shows the auto completion list at the current cursor position. * * @param chars_entered The number chars already entered for the word which is being completed. * @param entries A list of strings to show in the auto completion window. The variant with the * int part additionally takes an image id for each entry. Images must be registered * first with auto_completion_register_images. Use -1 as id when no image is needed. * The list should be sorted to make matching working properly. */ void auto_completion_show(size_t chars_entered, const std::vector<std::pair<int, std::string> >& entries); void auto_completion_show(size_t chars_entered, const std::vector<std::string>& entries); /** Can be used to cancel auto completion while it is in progress (i.e. during handling an * auto completion event. Has no effect otherwise. */ void auto_completion_cancel(); /** Used to set a few simple options for auto completion. * * @param ignore_case If true matching of characters to list members is not case sensitive. * @param choose_single If true and only one entry is in the auto completion list then this entry * is automatically used without showing the list. * @param auto_hide If true the list will automatically be hidden when there's no matching entry left. * @param drop_rest_of_word When an item is selected, any word characters following the caret are * first erased if this parameter is set true. * @param cancel_at_start If true the list is hidden when the caret moves to position it was when * auto completion started. */ void auto_completion_options(bool ignore_case, bool choose_single, bool auto_hide, bool drop_rest_of_word, bool cancel_at_start); /** Configures the maximum size of the auto completion list. If not set then the largest entry * in the list is used to determine the total width and the height is set to show 5 entries. * * @param width The number of characters to show at most. Longer entries will be shorted using ellipses. * @param height The number of entries (rows) to show. If there are more entries a vertical scrollbar is shown. */ void auto_completion_max_size(int width, int height); /** Used to load images (png or xpm type) into the editor and associate them with image ids, which can * be used to display them together with the text in the auto completion list. * * @param images A list of image file names that get loaded. */ void auto_completion_register_images(const std::vector<std::pair<int, std::string> >& images); /** Returns true if auto completion is currently active (i.e. the list is visible). */ bool auto_completion_active(); /** The characters in the given string automatically cancel auto completion. By default no stops are defined. */ void auto_completion_stops(const std::string& stops); /** If a fill-up character is typed while auto completion is active, the currently selected entry * item in the list is added into the document, then the fill-up character is added. By default * there is no fill-up character defined. */ void auto_completion_fillups(const std::string& fillups); /** Show the editor's calltip window close to the given position or hide it. */ void show_calltip(bool show, size_t position, const std::string& value); /** Sets the EOL mode used by the editor. If @convert is true all lines in the document are converted * to use the new line ending. */ void set_eol_mode(mforms::EndOfLineMode mode, bool convert = false); /** Sets a context menu to be attached to the editor, to be shown on right click. Note: Ownership of the context menu remains with the caller and it will not be freed when this object is deleted. */ void set_context_menu(Menu* menu) { _context_menu = menu; }; /** Returns the context menu object attached to the editor. */ Menu* get_context_menu() { return _context_menu; } /** Returns the host which is controlling this editor instance (if any). */ void* get_host() { return _host; } /** Direct access to the editor backend, for everything not covered here. */ sptr_t send_editor(unsigned int message, uptr_t wParam, sptr_t lParam); #ifndef SWIG /** Signal emitted when content is edited * Parameters are: * The (byte) position in the text where the change happened. * The length of the change (in bytes). * The number of lines which have been added (if positive) or removed (if negative). * True if text was inserted. */ boost::signals2::signal<void(Sci_Position, Sci_Position, Sci_Position, bool)>* signal_changed() { return &_change_event; } /** Signal emitted when the user clicks on the gutter. * Parameters are: * The margin (part of the gutter) the click occurred (0 = line numbers, 1 = markers, 2 = folding). * The line in which this happened. * The modifier keys that were pressed. */ boost::signals2::signal<void(size_t, size_t, mforms::ModifierKey)>* signal_gutter_clicked() { return &_gutter_clicked_event; } /** Event sent when auto completion notifications from Scintilla come in. * Parameters are: * The type of auto completion that happened. Only used with AutoCompletionSelection. * The start position of the word being completed. Only used with AutoCompletionSelection. * The text of the selection. */ boost::signals2::signal<void(AutoCompletionEventType, size_t, const std::string&)>* signal_auto_completion() { return &_auto_completion_event; }; /** Signal emitted when the user keeps the mouse in one position for the dwell period or when * when new events occur (text insertion, mouse move etc.) after dwelling started. * Parameters are: * Flag that tells if we are start dwelling or stop it. * The position closest to the mouse pointer. * x and y client coordinates where the mouse lingered. */ boost::signals2::signal<void(bool, size_t, int, int)>* signal_dwell() { return &_dwell_event; } /** Signal emitted when the user typed an ordinary text character (as opposed to a command character). * It can be used e.g. to trigger auto completion. * Parameter is: * The character code. */ boost::signals2::signal<void(int)>* signal_char_added() { return &_char_added_event; } /** Signal emitted when the Scintilla backend removes a set marker (e.g. on editing, pasting, manual marker * setting). * Parameters are: * A vector of line + markup pairs. * A flag telling if those markers where deleted or only updated (moved). */ boost::signals2::signal<void(const LineMarkupChangeset& changeset, bool deleted)>* signal_marker_changed() { return &_marker_changed_event; } boost::signals2::signal<bool(mforms::KeyCode code, mforms::ModifierKey modifier, const std::string& text)>* key_event_signal() { return &_key_event_signal; }; /** Signal emitted when the control loses input focus. */ boost::signals2::signal<void()>* signal_lost_focus() { return &_signal_lost_focus; } #ifndef DOXYGEN_SHOULD_SKIP_THIS /** Called by the platform code forwarding us all scintilla notifications, so we can act on them. */ void on_notify(SCNotification* notification); /** Called by the platform code forwarding us all scintilla commands, so we can act on them. */ void on_command(int command); void lost_focus(); bool key_event(mforms::KeyCode code, mforms::ModifierKey modifier, const std::string& text); virtual void resize() override; #endif #endif private: CodeEditorImplPtrs* _code_editor_impl; Menu* _context_menu; FindPanel* _find_panel; std::map<int, std::map<std::string, std::string>> _currentStyles; // Loaded styles for the currently configured langugage. void* _host; bool _scroll_on_resize; bool _auto_indent; MarginSizes _marginSize; void setupMarker(int marker, const std::string& name); void handleMarkerDeletion(size_t position, size_t length); void handleMarkerMove(Sci_Position position, Sci_Position linesAdded); char32_t getCharAt(size_t position); void updateBraceHighlighting(); bool ensureImage(std::string const& name); void loadConfiguration(SyntaxHighlighterLanguage language); virtual void handle_notification(const std::string &name, void *sender, base::NotificationInfo &info) override; boost::signals2::signal<void(Sci_Position, Sci_Position, Sci_Position, bool)> _change_event; boost::signals2::signal<void(size_t, size_t, mforms::ModifierKey)> _gutter_clicked_event; boost::signals2::signal<void(AutoCompletionEventType, size_t, const std::string&)> _auto_completion_event; boost::signals2::signal<void(bool, size_t, int, int)> _dwell_event; boost::signals2::signal<void(int)> _char_added_event; boost::signals2::signal<void()> _signal_lost_focus; boost::signals2::signal<void(const LineMarkupChangeset& changeset, bool deleted)> _marker_changed_event; boost::signals2::signal<bool(mforms::KeyCode code, mforms::ModifierKey modifier, const std::string& text)> _key_event_signal; std::function<void(CodeEditor*, bool)> _show_find_panel; }; };