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;
};
};