library/forms/code_editor.cpp (1,310 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
*/
#include <unordered_set>
#include "base/log.h"
#include "base/drawing.h"
#include "base/string_utilities.h"
#include "base/file_utilities.h"
#include "base/notifications.h"
#include "base/drawing.h"
#include "base/xml_functions.h"
#include "SciLexer.h"
#include "mforms/mforms.h"
#include "mforms/utilities.h"
#include "mysql/MySQLRecognizerCommon.h"
#include "SymbolTable.h"
DEFAULT_LOG_DOMAIN(DOMAIN_MFORMS_BE)
using namespace mforms;
using namespace base;
// Marker ID assignments. Markers with higher number overlay lower ones.
// Note: the order here matches the LineMarkup enum, so we can directly use the enum as marker flags.
// The LineMarkup is a bitmask (so we can create what Scintilla calls a marker set).
#define CE_STATEMENT_MARKER 0
#define CE_ERROR_MARKER 1
#define CE_BREAKPOINT_MARKER 2
#define CE_BREAKPOINT_HIT_MARKER 3
#define CE_CURRENT_LINE_MARKER 4
#define CE_ERROR_CONTINUE_MARKER 5
#define AC_LIST_SEPARATOR '\x19' // Unused codes as separators.
#define AC_TYPE_SEPARATOR '\x18'
#define ERROR_INDICATOR INDIC_CONTAINER
#define ERROR_INDICATOR_VALUE 42 // Arbitrary value.
typedef struct {
bool hiresImage;
int width;
int height;
void *data;
} ImageRecord;
static std::map<std::string, ImageRecord> registeredImages; // Registered RGBA images used in all editors.
//----------------- CodeEditorConfig -----------------------------------------------------------------------------------
CodeEditorConfig::CodeEditorConfig(SyntaxHighlighterLanguage language) {
_used_language = language;
_xmlDocument = nullptr;
_xmlLanguageElement = nullptr;
std::string lexer;
std::string override_lexer;
switch (language) {
case mforms::LanguageMySQL56:
override_lexer = "SCLEX_MYSQL_56";
lexer = "SCLEX_MYSQL";
break;
case mforms::LanguageMySQL57:
override_lexer = "SCLEX_MYSQL_57";
lexer = "SCLEX_MYSQL";
break;
case mforms::LanguageMySQL80:
override_lexer = "SCLEX_MYSQL_80";
lexer = "SCLEX_MYSQL";
break;
case mforms::LanguageHtml:
lexer = "SCLEX_HTML";
break;
case mforms::LanguagePython:
lexer = "SCLEX_PYTHON";
break;
case mforms::LanguageCpp:
lexer = "SCLEX_CPP";
break;
case mforms::LanguageJS:
lexer = "SCLEX_CPP";
override_lexer = "SCLEX_CPP_JS";
break;
case mforms::LanguageJson:
lexer = "SCLEX_CPP";
override_lexer = "SCLEX_CPP_JSON";
break;
default:
return;
}
// Load the user's config file if it exists, otherwise use the default one.
std::string config_file =
mforms::Utilities::get_special_folder(mforms::ApplicationData) + "/MySQL/Workbench/code_editor.xml";
if (!base::file_exists(config_file))
config_file = App::get()->get_resource_path("") + "/data/code_editor.xml";
try {
_xmlDocument = base::xml::loadXMLDoc(config_file);
if (_xmlDocument == nullptr) {
logError("Code Editor Config: cannot load configuration file \"%s\"\n", config_file.c_str());
return;
}
} catch (std::runtime_error& re) {
logError("Code Editor Config: cannot load configuration file \"%s\":\n\t%s \n", config_file.c_str(), re.what());
return;
}
auto rootElement = base::xml::getXmlRoot(_xmlDocument);
if (!base::xml::nameIs(rootElement, "languages")) {
logError("Code Editor: invalid configuration file \"%s\"\n", config_file.c_str());
return;
}
{
auto current = rootElement->children;
// Load the available language identifiers. All remaining values are loaded on demand.
while (current != nullptr) {
if (base::xml::nameIs(current, "language")) {
std::string languageName = base::xml::getProp(current, "name");
if (languageName == lexer)
_xmlLanguageElement = current;
_languages.push_back(languageName);
}
current = current->next;
}
}
if (_xmlLanguageElement == nullptr) {
logWarning(
"Code Editor: could not find settings for language %s in configuration file "
"\"%s\"\n",
lexer.c_str(), config_file.c_str());
return;
}
parse_properties();
parse_settings();
parse_keywords();
parse_styles();
// check if there's another config section containing values that should override the base one
if (!override_lexer.empty() && override_lexer != lexer) {
bool found = false;
// Load the available language identifiers. All remaining values are loaded on demand.
auto current = rootElement->children;
while (current != nullptr) {
if (base::xml::nameIs(current, "language")) {
std::string languageName = base::xml::getProp(current, "name");
if (languageName == override_lexer) {
_xmlLanguageElement = current;
found = true;
break;
}
}
current = current->next;
}
if (found) {
parse_properties();
parse_settings();
parse_keywords();
parse_styles();
}
}
}
//----------------------------------------------------------------------------------------------------------------------
CodeEditorConfig::~CodeEditorConfig() {
if (_xmlDocument != nullptr)
xmlFree(_xmlDocument);
_xmlDocument = nullptr;
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditorConfig::parse_properties() {
auto current = _xmlLanguageElement->children;
while (current != nullptr) {
if (base::xml::nameIs(current, "property")) {
std::string pName = base::xml::getProp(current, "name");
std::string pValue = base::xml::getProp(current, "value");
if (!pName.empty() && !pValue.empty())
_properties[pName] = pValue;
}
current = current->next;
}
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditorConfig::parse_settings() {
auto current = _xmlLanguageElement->children;
while (current != nullptr) {
if (base::xml::nameIs(current, "setting")) {
std::string pName = base::xml::getProp(current, "name");
std::string pValue = base::xml::getProp(current, "value");
if (!pName.empty() && !pValue.empty())
_settings[pName] = pValue;
}
current = current->next;
}
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditorConfig::parse_keywords() {
auto current = _xmlLanguageElement->children;
while (current != nullptr) {
if (base::xml::nameIs(current, "keywords")) {
std::string pName = base::xml::getProp(current, "name");
std::string text = base::xml::getContentRecursive(current);
if (!pName.empty() && !text.empty())
_keywords[pName] = text;
}
current = current->next;
}
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditorConfig::parse_styles() {
auto current = _xmlLanguageElement->children;
while (current != nullptr) {
if (base::xml::nameIs(current, "style")) {
int id = (int)std::strtol(base::xml::getProp(current, "id").c_str(), nullptr, 10);
if (id < 0) {
current = current->next;
continue;
}
std::map<std::string, std::string> entries;
auto attribute = current->properties;
while (attribute != nullptr) {
if (base::xml::nameIs(attribute, "id")) {
attribute = attribute->next;
continue;
}
entries[std::string((const char*)attribute->name)] = base::xml::getContent(attribute->children);
attribute = attribute->next;
}
_styles[id] = entries;
}
current = current->next;
}
}
//----------------- CodeEditor -----------------------------------------------------------------------------------------
CodeEditor::CodeEditor(void* host, bool showInfo) : _host(host) {
_code_editor_impl = &ControlFactory::get_instance()->_code_editor_impl;
_code_editor_impl->create(this, showInfo);
_code_editor_impl->send_editor(this, SCI_SETCODEPAGE, SC_CP_UTF8, 0);
_context_menu = NULL;
_find_panel = NULL;
_scroll_on_resize = true;
_auto_indent = false;
scoped_connect(Form::main_form()->signal_deactivated(), std::bind(&CodeEditor::auto_completion_cancel, this));
base::NotificationCenter::get()->add_observer(this, "GNColorsChanged");
// Load the marker images for folding, debugging, error marker etc.
setupMarker(CE_STATEMENT_MARKER, "editor_statement.png");
setupMarker(CE_ERROR_MARKER, "editor_error.png");
setupMarker(CE_BREAKPOINT_MARKER, "editor_breakpoint.png");
setupMarker(CE_BREAKPOINT_HIT_MARKER, "editor_breakpoint_hit.png");
setupMarker(CE_CURRENT_LINE_MARKER, "editor_current_pos.png");
setupMarker(CE_ERROR_CONTINUE_MARKER, "editor_continue_on_error.png");
// Determine fixed settings that don't change with the OS appearance.
// Margin 0: line numbers.
_code_editor_impl->send_editor(this, SCI_SETMARGINTYPEN, 0, SC_MARGIN_NUMBER);
#if defined(__APPLE__)
_code_editor_impl->send_editor(this, SCI_STYLESETSIZE, STYLE_LINENUMBER, static_cast<sptr_t>(10));
#else
_code_editor_impl->send_editor(this, SCI_STYLESETSIZE, STYLE_LINENUMBER, static_cast<sptr_t>(8));
#endif
sptr_t lineNumberStyleWidth =
_code_editor_impl->send_editor(this, SCI_TEXTWIDTH, STYLE_LINENUMBER, (sptr_t) "_9999");
_code_editor_impl->send_editor(this, SCI_SETMARGINWIDTHN, 0, lineNumberStyleWidth);
_code_editor_impl->send_editor(this, SCI_SETMARGINSENSITIVEN, 0, 0);
// Margin 1: all kind of markers (debugging, current line, code line indicator etc.).
_code_editor_impl->send_editor(this, SCI_SETMARGINWIDTHN, 1, 16);
_code_editor_impl->send_editor(this, SCI_SETMARGINSENSITIVEN, 1, 1);
// Margin 2: folding.
_code_editor_impl->send_editor(this, SCI_SETPROPERTY, (uptr_t) "fold", (sptr_t) "1");
_code_editor_impl->send_editor(this, SCI_SETMARGINWIDTHN, 2, 13);
_code_editor_impl->send_editor(this, SCI_SETAUTOMATICFOLD, SC_AUTOMATICFOLD_SHOW | SC_AUTOMATICFOLD_CHANGE, 0);
_code_editor_impl->send_editor(this, SCI_SETMARGINMASKN, 2, SC_MASK_FOLDERS);
_code_editor_impl->send_editor(this, SCI_SETMARGINSENSITIVEN, 2, 1); // Margin is clickable.
_code_editor_impl->send_editor(this, SCI_MARKERDEFINE, SC_MARKNUM_FOLDEROPEN, SC_MARK_CIRCLEMINUS);
_code_editor_impl->send_editor(this, SCI_MARKERDEFINE, SC_MARKNUM_FOLDER, SC_MARK_CIRCLEPLUS);
_code_editor_impl->send_editor(this, SCI_MARKERDEFINE, SC_MARKNUM_FOLDERSUB, SC_MARK_VLINE);
_code_editor_impl->send_editor(this, SCI_MARKERDEFINE, SC_MARKNUM_FOLDERTAIL, SC_MARK_LCORNERCURVE);
_code_editor_impl->send_editor(this, SCI_MARKERDEFINE, SC_MARKNUM_FOLDEREND, SC_MARK_CIRCLEPLUSCONNECTED);
_code_editor_impl->send_editor(this, SCI_MARKERDEFINE, SC_MARKNUM_FOLDEROPENMID, SC_MARK_CIRCLEMINUSCONNECTED);
_code_editor_impl->send_editor(this, SCI_MARKERDEFINE, SC_MARKNUM_FOLDERMIDTAIL, SC_MARK_TCORNERCURVE);
// Margin 3: empty, to provide some spacing between the folder margin and the text.
_code_editor_impl->send_editor(this, SCI_SETMARGINTYPEN, 3, SC_MARGIN_BACK);
_code_editor_impl->send_editor(this, SCI_SETMARGINWIDTHN, 3, 5);
_code_editor_impl->send_editor(this, SCI_SETMARGINSENSITIVEN, 3, 0);
// Init markers & indicators for highlighting of syntax errors.
_code_editor_impl->send_editor(this, SCI_INDICSETUNDER, ERROR_INDICATOR, 1);
_code_editor_impl->send_editor(this, SCI_INDICSETSTYLE, ERROR_INDICATOR, INDIC_SQUIGGLE);
// Other settings.
_code_editor_impl->send_editor(this, SCI_SETEXTRAASCENT, 3, 0);
_code_editor_impl->send_editor(this, SCI_SETEXTRADESCENT, 3, 0);
_code_editor_impl->send_editor(this, SCI_SETCARETLINEVISIBLE, 1, 0);
_code_editor_impl->send_editor(this, SCI_SETCARETWIDTH, 2, 0);
// - Tabulators + indentation
_code_editor_impl->send_editor(this, SCI_SETTABINDENTS, 1, 0);
_code_editor_impl->send_editor(this, SCI_SETBACKSPACEUNINDENTS, 1, 0);
// - Call tips
_code_editor_impl->send_editor(this, SCI_SETMOUSEDWELLTIME, 200, 0);
// - Line ending + scrolling.
_code_editor_impl->send_editor(this, SCI_SETSCROLLWIDTHTRACKING, 1, 0);
_code_editor_impl->send_editor(this, SCI_SETEOLMODE, SC_EOL_LF, 0);
// - Auto completion
_code_editor_impl->send_editor(this, SCI_AUTOCSETSEPARATOR, AC_LIST_SEPARATOR, 0);
_code_editor_impl->send_editor(this, SCI_AUTOCSETTYPESEPARATOR, AC_TYPE_SEPARATOR, 0);
updateColors();
}
//----------------------------------------------------------------------------------------------------------------------
CodeEditor::~CodeEditor() {
base::NotificationCenter::get()->remove_observer(this);
delete _find_panel;
auto_completion_cancel();
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditor::updateColors() {
bool darkMode = App::get()->isDarkModeActive();
base::Color color = base::Color::getSystemColor(base::TextBackgroundColor);
long mainBackground = color.toBGR();
_code_editor_impl->send_editor(this, SCI_STYLESETBACK, STYLE_DEFAULT, mainBackground);
// Propagate the new background color to all editor text styles.
for (int i = 0; i < STYLE_DEFAULT; ++i)
_code_editor_impl->send_editor(this, SCI_STYLESETBACK, i, mainBackground);
for (int i = STYLE_LASTPREDEFINED + 1; i < STYLE_MAX; ++i)
_code_editor_impl->send_editor(this, SCI_STYLESETBACK, i, mainBackground);
for (int n = SC_MARKNUM_FOLDEREND; n <= SC_MARKNUM_FOLDEROPEN; ++n) {
_code_editor_impl->send_editor(this, SCI_MARKERSETFORE, n, mainBackground); // Front/back meaning is reversed here.
_code_editor_impl->send_editor(this, SCI_MARKERSETBACK, n, 0xA0B0B0);
}
_code_editor_impl->send_editor(this, SCI_STYLESETFORE, STYLE_LINENUMBER, darkMode ? 0x808080 : 0x907522);
_code_editor_impl->send_editor(this, SCI_STYLESETBACK, STYLE_LINENUMBER, mainBackground);
_code_editor_impl->send_editor(this, SCI_INDICSETFORE, ERROR_INDICATOR, darkMode ? 0x281FFF : 0x2119D0);
// Other settings.
color = Color::getSystemColor(base::SelectedTextBackgroundColor);
_code_editor_impl->send_editor(this, SCI_SETSELBACK, 1, color.toBGR());
#ifndef __APPLE__
_code_editor_impl->send_editor(this, SCI_SETSELALPHA, 64, 0);
#endif
if (darkMode) {
_code_editor_impl->send_editor(this, SCI_SETCARETLINEBACK, 0xFFFFFF, 0);
_code_editor_impl->send_editor(this, SCI_SETCARETLINEBACKALPHA, 0x38, 0);
_code_editor_impl->send_editor(this, SCI_SETCARETFORE, 0xAEAEAE, 0);
} else {
_code_editor_impl->send_editor(this, SCI_SETCARETLINEBACK, 0x000000, 0);
_code_editor_impl->send_editor(this, SCI_SETCARETLINEBACKALPHA, 0x10, 0);
_code_editor_impl->send_editor(this, SCI_SETCARETFORE, 0x404040, 0);
}
// - Call tips
color = base::Color::getSystemColor(base::TextColor);
_code_editor_impl->send_editor(this, SCI_CALLTIPSETFORE, color.toBGR(), 0);
_code_editor_impl->send_editor(this, SCI_CALLTIPSETBACK, darkMode ? 0x404040 : 0xF8F8F8, 0);
auto applyStyle = [&](int id, auto &values) {
std::string value = darkMode ? values["fore-color-dark"] : values["fore-color-light"];
if (value.empty())
value = values["fore-color"]; // Backwards compatibility.
if (!value.empty()) {
Color color = Color::parse(value);
_code_editor_impl->send_editor(this, SCI_STYLESETFORE, id, color.toBGR());
}
value = darkMode ? values["back-color-dark"] : values["back-color-light"];
if (!value.empty()) {
Color color = Color::parse(value);
_code_editor_impl->send_editor(this, SCI_STYLESETBACK, id, color.toBGR());
}
value = base::tolower(values["bold"]);
if (!value.empty()) {
bool flag = value == "1" || value == "yes" || value == "true";
_code_editor_impl->send_editor(this, SCI_STYLESETBOLD, id, flag);
}
value = base::tolower(values["italic"]);
if (!value.empty()) {
bool flag = value == "1" || value == "yes" || value == "true";
_code_editor_impl->send_editor(this, SCI_STYLESETITALIC, id, flag);
}
};
// First load the default style and apply that to all text style entries. Then loop again for individual settings.
for (auto &style : _currentStyles) {
if (style.first == 0) {
for (int i = 0; i < STYLE_DEFAULT; ++i)
applyStyle(i, style.second);
for (int i = STYLE_LASTPREDEFINED + 1; i < STYLE_MAX; ++i)
applyStyle(i, style.second);
break;
}
}
for (auto &style : _currentStyles) {
if (style.first == 0)
continue;
applyStyle(style.first, style.second);
}
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditor::setWidth(EditorMargin margin, int size, const std::string& adjustText) {
if (!adjustText.empty())
size = (int)_code_editor_impl->send_editor(this, SCI_TEXTWIDTH, STYLE_LINENUMBER, (sptr_t)adjustText.c_str());
switch (margin) {
case LineNumberMargin:
_code_editor_impl->send_editor(this, SCI_SETMARGINWIDTHN, 0, size);
break;
case MarkersMargin:
_code_editor_impl->send_editor(this, SCI_SETMARGINWIDTHN, 1, size);
break;
case FolderMargin:
_code_editor_impl->send_editor(this, SCI_SETMARGINWIDTHN, 2, size);
break;
case TextMargin:
_code_editor_impl->send_editor(this, SCI_SETMARGINWIDTHN, 3, size);
break;
default:
break;
}
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditor::setColor(EditorMargin margin, base::Color color, bool foreground) {
switch (margin) {
case FolderMargin:
case LineNumberMargin:
case TextMargin:
if (foreground)
_code_editor_impl->send_editor(this, SCI_STYLESETFORE, STYLE_LINENUMBER, color.toRGB());
else
_code_editor_impl->send_editor(this, SCI_STYLESETBACK, STYLE_LINENUMBER, color.toRGB());
break;
case MarkersMargin:
for (int n = 25; n < 32; ++n) { // Markers 25..31 are reserved for folding.
if (foreground)
_code_editor_impl->send_editor(this, SCI_MARKERSETFORE, n, color.toRGB());
else
_code_editor_impl->send_editor(this, SCI_MARKERSETBACK, n, color.toRGB());
}
break;
default:
break;
}
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditor::showMargin(EditorMargin margin, bool show) {
sptr_t size = 0;
const sptr_t defaultSize = 16;
switch (margin) {
case LineNumberMargin:
if (!show)
_marginSize.margin1 = _code_editor_impl->send_editor(this, SCI_GETMARGINWIDTHN, 0, 0);
else {
size = _code_editor_impl->send_editor(this, SCI_GETMARGINWIDTHN, 0, 0);
if (size > 0)
break;
size = _marginSize.margin1 > 0 ? _marginSize.margin1 : defaultSize;
}
break;
case MarkersMargin:
if (!show)
_marginSize.margin2 = _code_editor_impl->send_editor(this, SCI_GETMARGINWIDTHN, 1, 0);
else {
size = _code_editor_impl->send_editor(this, SCI_GETMARGINWIDTHN, 1, 0);
if (size > 0)
break;
size = _marginSize.margin2 > 0 ? _marginSize.margin2 : defaultSize;
}
break;
case FolderMargin:
if (!show)
_marginSize.margin3 = _code_editor_impl->send_editor(this, SCI_GETMARGINWIDTHN, 2, 0);
else {
size = _code_editor_impl->send_editor(this, SCI_GETMARGINWIDTHN, 2, 0);
if (size > 0)
break;
size = _marginSize.margin3 > 0 ? _marginSize.margin3 : defaultSize;
}
break;
case TextMargin:
if (!show)
_marginSize.margin4 = _code_editor_impl->send_editor(this, SCI_GETMARGINWIDTHN, 3, 0);
else {
size = _code_editor_impl->send_editor(this, SCI_GETMARGINWIDTHN, 3, 0);
if (size > 0)
break;
size = _marginSize.margin4 > 0 ? _marginSize.margin4 : defaultSize;
}
break;
default:
break;
}
if (!show)
setWidth(margin, 0);
else
setWidth(margin, (int)size);
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditor::setMarginText(const std::string& str) {
setMarginText(str, line_count() - 1);
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditor::setScrollWidth(size_t width) {
_code_editor_impl->send_editor(this, SCI_SETSCROLLWIDTH, width, 0);
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditor::setMarginText(const std::string& str, size_t line) {
sptr_t size = _code_editor_impl->send_editor(this, SCI_GETMARGINWIDTHN, 3, 0);
sptr_t lineNumberStyleWidth =
_code_editor_impl->send_editor(this, SCI_TEXTWIDTH, STYLE_LINENUMBER, (sptr_t)str.c_str());
if (lineNumberStyleWidth > size)
_code_editor_impl->send_editor(this, SCI_SETMARGINWIDTHN, 3, lineNumberStyleWidth);
_code_editor_impl->send_editor(this, SCI_MARGINSETTEXT, line, (sptr_t)str.c_str());
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditor::set_text(const char* text) {
_code_editor_impl->send_editor(this, SCI_SETTEXT, 0, (sptr_t)text);
}
//----------------------------------------------------------------------------------------------------------------------
int CodeEditor::getLineHeight(int line) {
return (int)_code_editor_impl->send_editor(this, SCI_TEXTHEIGHT, line, 0);
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditor::set_value(const std::string& value) {
// When passing text as std::string we have a length and can hence use a different
// way to set the text in the control, which preserves embedded nulls.
_code_editor_impl->send_editor(this, SCI_CLEARALL, 0, 0);
_code_editor_impl->send_editor(this, SCI_APPENDTEXT, value.size(), (sptr_t)value.c_str());
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditor::set_text_keeping_state(const char* text) {
sptr_t caret_position = _code_editor_impl->send_editor(this, SCI_GETCURRENTPOS, 0, 0);
sptr_t selection_start = _code_editor_impl->send_editor(this, SCI_GETSELECTIONSTART, 0, 0);
sptr_t selection_end = _code_editor_impl->send_editor(this, SCI_GETSELECTIONEND, 0, 0);
sptr_t first_line = _code_editor_impl->send_editor(this, SCI_GETFIRSTVISIBLELINE, 0, 0);
_code_editor_impl->send_editor(this, SCI_SETTEXT, 0, (sptr_t)text);
_code_editor_impl->send_editor(this, SCI_SETCURRENTPOS, caret_position, 0);
_code_editor_impl->send_editor(this, SCI_SETSELECTIONSTART, selection_start, 0);
_code_editor_impl->send_editor(this, SCI_SETSELECTIONEND, selection_end, 0);
_code_editor_impl->send_editor(this, SCI_SETFIRSTVISIBLELINE, first_line, 0);
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditor::append_text(const char* text, size_t length) {
_code_editor_impl->send_editor(this, SCI_APPENDTEXT, length, (sptr_t)text);
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditor::replace_selected_text(const std::string& text) {
std::size_t start, length;
get_selection(start, length);
_code_editor_impl->send_editor(this, SCI_REPLACESEL, 0, (sptr_t)text.c_str());
_code_editor_impl->send_editor(this, SCI_SETSELECTIONSTART, start + text.length(), 0);
_code_editor_impl->send_editor(this, SCI_SETSELECTIONEND, start + text.length(), 0);
}
//----------------------------------------------------------------------------------------------------------------------
const std::string CodeEditor::get_text(bool selection_only) {
char* text = nullptr;
sptr_t length;
if (selection_only) {
length = _code_editor_impl->send_editor(this, SCI_GETSELTEXT, 0, 0);
if (length > 0) {
text = (char*)malloc(length);
}
if (text != nullptr) // Can be null if not memory is available.
_code_editor_impl->send_editor(this, SCI_GETSELTEXT, length, (sptr_t)text);
} else {
length = _code_editor_impl->send_editor(this, SCI_GETTEXTLENGTH, 0, 0) + 1;
if (length > 0) {
text = (char*)malloc(length);
}
if (text != nullptr)
_code_editor_impl->send_editor(this, SCI_GETTEXT, length, (sptr_t)text);
}
if (text != nullptr) {
std::string result(text, length - 1);
free(text);
return result;
}
return "";
}
//----------------------------------------------------------------------------------------------------------------------
const std::string CodeEditor::get_text_in_range(size_t start, size_t end) {
Sci_TextRange range;
range.chrg.cpMin = (long)start;
sptr_t length = _code_editor_impl->send_editor(this, SCI_GETTEXTLENGTH, 0, 0);
range.chrg.cpMax = (long)(end > start + length ? length - start : end);
range.lpstrText = (char*)malloc(end - start + 1); // Don't forget the 0 terminator.
_code_editor_impl->send_editor(this, SCI_GETTEXTRANGE, 0, (sptr_t)&range);
if (range.lpstrText == NULL)
return std::string();
std::string result(range.lpstrText, end - start);
free(range.lpstrText);
return result;
}
//----------------------------------------------------------------------------------------------------------------------
std::pair<const char*, std::size_t> CodeEditor::get_text_ptr() {
std::pair<const char*, std::size_t> result;
result.first = reinterpret_cast<const char *>(_code_editor_impl->send_editor(this, SCI_GETCHARACTERPOINTER, 0, 0));
result.second = _code_editor_impl->send_editor(this, SCI_GETTEXTLENGTH, 0, 0);
return result;
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditor::set_selection(std::size_t start, std::size_t length) {
_code_editor_impl->send_editor(this, SCI_SETSELECTIONSTART, start, 0);
_code_editor_impl->send_editor(this, SCI_SETSELECTIONEND, start + length, 0);
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditor::clear_selection() {
sptr_t current_pos = _code_editor_impl->send_editor(this, SCI_GETCURRENTPOS, 0, 0);
_code_editor_impl->send_editor(this, SCI_SETEMPTYSELECTION, current_pos, 0);
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditor::get_selection(std::size_t& start, std::size_t& length) {
start = _code_editor_impl->send_editor(this, SCI_GETSELECTIONSTART, 0, 0);
length = _code_editor_impl->send_editor(this, SCI_GETSELECTIONEND, 0, 0) - start;
}
//----------------------------------------------------------------------------------------------------------------------
bool CodeEditor::get_range_of_line(ssize_t line, ssize_t& start, ssize_t& end) {
start = _code_editor_impl->send_editor(this, SCI_POSITIONFROMLINE, line, 0);
end = _code_editor_impl->send_editor(this, SCI_GETLINEENDPOSITION, line, 0);
return start < 0 || end < 0;
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditor::setupMarker(int marker, const std::string& name) {
if (base::hasSuffix(name, ".xpm")) {
std::string path = App::get()->get_resource_path(name);
gchar* content = NULL;
if (g_file_get_contents(path.c_str(), &content, NULL, NULL)) {
_code_editor_impl->send_editor(this, SCI_MARKERDEFINEPIXMAP, marker, (sptr_t)content);
g_free(content);
}
} else { // png
if (ensureImage(name)) {
auto &data = registeredImages[name];
_code_editor_impl->send_editor(this, SCI_RGBAIMAGESETWIDTH, data.width, 0);
_code_editor_impl->send_editor(this, SCI_RGBAIMAGESETHEIGHT, data.height, 0);
_code_editor_impl->send_editor(this, SCI_RGBAIMAGESETSCALE, data.hiresImage ? 200 : 100, 0);
_code_editor_impl->send_editor(this, SCI_MARKERDEFINERGBAIMAGE, marker, reinterpret_cast<sptr_t>(data.data));
}
}
}
//----------------------------------------------------------------------------------------------------------------------
/**
* Called before lines are removed. Need to record disappearing markers.
*/
void CodeEditor::handleMarkerDeletion(size_t position, size_t length) {
if (length == 0)
return;
LineMarkupChangeset changeset;
if (length == static_cast<size_t>(_code_editor_impl->send_editor(this, SCI_GETLENGTH, 0, 0))) {
// Clean editor? Send empty changeset to signal that all markers can go.
_marker_changed_event(changeset, true);
return;
}
sptr_t currentLine = _code_editor_impl->send_editor(this, SCI_LINEFROMPOSITION, position, 0);
sptr_t endLine = _code_editor_impl->send_editor(this, SCI_LINEFROMPOSITION, position + length - 1, 0);
// Don't include the first line as Scintilla merges current and last line markers, so we have to update them.
++currentLine;
currentLine = _code_editor_impl->send_editor(this, SCI_MARKERNEXT, currentLine, LineMarkupAll);
while (currentLine > -1 && currentLine <= endLine) {
LineMarkup markup = (LineMarkup)_code_editor_impl->send_editor(this, SCI_MARKERGET, currentLine, LineMarkupAll);
LineMarkupChangeEntry entry = {(int)currentLine, 0, markup};
changeset.push_back(entry);
// MARKERNEXT effectively searches lines for the given markers, the docs say.
currentLine = _code_editor_impl->send_editor(this, SCI_MARKERNEXT, currentLine + 1, LineMarkupAll);
}
if (!changeset.empty())
_marker_changed_event(changeset, true);
}
//----------------------------------------------------------------------------------------------------------------------
/**
* Called after an edit action took place. We have to record markers that got moved by that action.
*/
void CodeEditor::handleMarkerMove(Sci_Position position, Sci_Position linesAdded) {
if (linesAdded == 0)
return;
LineMarkupChangeset changeset;
sptr_t currentLine = _code_editor_impl->send_editor(this, SCI_LINEFROMPOSITION, position, 0);
if (linesAdded < 0) {
// Lines have been deleted and markers merged. Since we don't know which have been combined remove them all
// and send a separate deletion event for this case.
_code_editor_impl->send_editor(this, SCI_MARKERDELETE, currentLine, -1);
changeset.push_back({(int)currentLine, 0, LineMarkupAll});
_marker_changed_event(changeset, true);
changeset.clear();
}
// Ignore the first line if it has been edited after the line start.
if (position > static_cast<Sci_Position>(_code_editor_impl->send_editor(this, SCI_POSITIONFROMLINE, currentLine, 0)))
++currentLine;
currentLine = _code_editor_impl->send_editor(this, SCI_MARKERNEXT, currentLine, LineMarkupAll);
while (currentLine > -1) {
LineMarkup markup = (LineMarkup)_code_editor_impl->send_editor(this, SCI_MARKERGET, currentLine, LineMarkupAll);
LineMarkupChangeEntry entry = {(int)(currentLine - linesAdded), (int)currentLine, markup};
changeset.push_back(entry);
currentLine = _code_editor_impl->send_editor(this, SCI_MARKERNEXT, currentLine + 1, LineMarkupAll);
}
if (!changeset.empty())
_marker_changed_event(changeset, false);
}
//----------------------------------------------------------------------------------------------------------------------
char32_t CodeEditor::getCharAt(size_t position) {
return static_cast<char32_t>(_code_editor_impl->send_editor(this, SCI_GETCHARAT, position, 0));
}
//----------------------------------------------------------------------------------------------------------------------
static const std::unordered_set<char32_t> braces = { '(', '{', '[', '<', ')', '}', ']', '>' };
void CodeEditor::updateBraceHighlighting() {
size_t caretPos = get_caret_pos();
ssize_t brace1Pos = INVALID_POSITION;
ssize_t brace2Pos = INVALID_POSITION;
// Highlight an opening brace regardless of the side the caret is, unless another opening brace follows directly
// (and similary for closing braces).
char32_t c = getCharAt(caretPos);
if (braces.count(c) > 0)
brace1Pos = caretPos;
else if (caretPos > 0) {
c = getCharAt(caretPos - 1);
if (braces.count(c) > 0)
brace1Pos = caretPos - 1;
}
if (brace1Pos > INVALID_POSITION) {
brace2Pos = _code_editor_impl->send_editor(this, SCI_BRACEMATCH, brace1Pos, 0);
if (brace2Pos == INVALID_POSITION)
_code_editor_impl->send_editor(this, SCI_BRACEBADLIGHT, brace1Pos, 0);
else
_code_editor_impl->send_editor(this, SCI_BRACEHIGHLIGHT, brace1Pos, brace2Pos);
} else
_code_editor_impl->send_editor(this, SCI_BRACEHIGHLIGHT, brace1Pos, brace2Pos);
}
//----------------------------------------------------------------------------------------------------------------------
/**
* Ensures the given image is loaded from disk and stored in our image list.
* Returns true if the image exists.
*/
bool CodeEditor::ensureImage(std::string const& name) {
if (registeredImages.find(name) != registeredImages.end())
return true;
cairo_surface_t* image = Utilities::load_icon(name);
if (image == nullptr)
return false;
if (cairo_surface_status(image) != CAIRO_STATUS_SUCCESS) {
cairo_surface_destroy(image);
return false;
}
int width = cairo_image_surface_get_width(image);
int height = cairo_image_surface_get_height(image);
unsigned char* data = cairo_image_surface_get_data(image);
// We not only need to keep the data around for the lifetime of the editor we also have
// to swap blue and red.
unsigned char *target = reinterpret_cast<unsigned char *>(malloc(4 * width * height));
if (target != nullptr) {
ImageRecord entry = {
Utilities::is_hidpi_icon(image),
cairo_image_surface_get_width(image),
cairo_image_surface_get_height(image),
target
};
registeredImages[name] = entry;
int j = 0;
while (j < 4 * width * height) {
target[j] = data[j + 2];
target[j + 1] = data[j + 1];
target[j + 2] = data[j];
target[j + 3] = data[j + 3];
j += 4;
}
}
cairo_surface_destroy(image);
return true;
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditor::loadConfiguration(SyntaxHighlighterLanguage language) {
CodeEditorConfig config(language);
// Keywords.
if (language == LanguageMySQL56 || language == LanguageMySQL57 || language == LanguageMySQL80) {
MySQLVersion version;
switch (language) {
case LanguageMySQL56:
version = MySQLVersion::MySQL56;
break;
case LanguageMySQL57:
version = MySQLVersion::MySQL57;
break;
default:
version = MySQLVersion::MySQL80;
break;
}
auto &functions = MySQLSymbolInfo::systemFunctionsForVersion(version);
std::string list;
for (auto const& name : functions)
list += base::tolower(name) + " "; // Keyword matching in Scintilla uses lower case compare.
_code_editor_impl->send_editor(this, SCI_SETKEYWORDS, 3, (sptr_t)list.c_str());
auto &keywords = MySQLSymbolInfo::keywordsForVersion(version);
list = "";
for (auto const& name : keywords)
list += base::tolower(name) + " ";
_code_editor_impl->send_editor(this, SCI_SETKEYWORDS, 1, (sptr_t)list.c_str());
} else {
std::map<std::string, std::string> keywords = config.get_keywords();
// Key word list sets are from currently active lexer, so that must be set before calling here.
sptr_t length = _code_editor_impl->send_editor(this, SCI_DESCRIBEKEYWORDSETS, 0, 0);
if (length > 0) {
char* keyword_sets = (char*)malloc(length + 1);
_code_editor_impl->send_editor(this, SCI_DESCRIBEKEYWORDSETS, 0, (sptr_t)keyword_sets);
std::vector<std::string> keyword_list_names = base::split(keyword_sets, "\n");
free(keyword_sets);
for (auto iterator : keywords) {
std::string list_name = iterator.first;
int list_index = base::index_of(keyword_list_names, list_name);
if (list_index > -1)
_code_editor_impl->send_editor(this, SCI_SETKEYWORDS, list_index, reinterpret_cast<sptr_t>(iterator.second.c_str()));
}
}
}
// Properties.
for (auto const& property : config.get_properties()) {
_code_editor_impl->send_editor(this, SCI_SETPROPERTY,
reinterpret_cast<uptr_t>(property.first.c_str()), reinterpret_cast<sptr_t>(property.second.c_str()));
}
// Settings.
for (auto const& setting : config.get_settings()) {
if (setting.first == "usetabs") {
int value = base::atoi<int>(setting.second, 0);
_code_editor_impl->send_editor(this, SCI_SETUSETABS, value, 0);
} else if (setting.first == "tabwidth") {
int value = base::atoi<int>(setting.second, 0);
_code_editor_impl->send_editor(this, SCI_SETTABWIDTH, value, 0);
} else if (setting.first == "indentation") {
int value = base::atoi<int>(setting.second, 0);
_code_editor_impl->send_editor(this, SCI_SETINDENT, value, 0);
}
}
// Styles.
_currentStyles = config.get_styles();
updateColors();
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditor::handle_notification(const std::string &name, void *sender, NotificationInfo &info) {
if (name == "GNColorsChanged")
updateColors();
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditor::set_language(SyntaxHighlighterLanguage language) {
switch (language) {
case mforms::LanguageMySQL56:
case mforms::LanguageMySQL57:
case mforms::LanguageMySQL80:
_code_editor_impl->send_editor(this, SCI_SETLEXER, SCLEX_MYSQL, 0);
break;
case mforms::LanguageHtml:
_code_editor_impl->send_editor(this, SCI_SETLEXER, SCLEX_HTML, 0);
break;
case mforms::LanguagePython:
_code_editor_impl->send_editor(this, SCI_SETLEXER, SCLEX_PYTHON, 0);
break;
case mforms::LanguageCpp:
case mforms::LanguageJS:
case mforms::LanguageJson:
_code_editor_impl->send_editor(this, SCI_SETLEXER, SCLEX_CPP, 0);
break;
default:
// No (known) language. Syntax highlighting will be switched off.
_code_editor_impl->send_editor(this, SCI_SETLEXER, SCLEX_NULL, 0);
return;
}
loadConfiguration(language);
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditor::show_markup(LineMarkup markup, size_t line) {
// The marker mask contains one bit for each set marker (0..31).
sptr_t marker_mask = _code_editor_impl->send_editor(this, SCI_MARKERGET, line, 0);
sptr_t new_marker_mask = 0;
if ((markup & mforms::LineMarkupStatement) != 0) {
if ((marker_mask & LineMarkupStatement) == 0)
new_marker_mask |= LineMarkupStatement;
}
if ((markup & mforms::LineMarkupErrorContinue) != 0) {
if ((marker_mask & LineMarkupErrorContinue) == 0)
new_marker_mask |= LineMarkupErrorContinue;
}
if ((markup & mforms::LineMarkupError) != 0) {
if ((marker_mask & LineMarkupError) == 0)
new_marker_mask |= LineMarkupError;
}
if ((markup & mforms::LineMarkupBreakpoint) != 0) {
if ((marker_mask & LineMarkupBreakpoint) == 0)
new_marker_mask |= LineMarkupBreakpoint;
}
if ((markup & mforms::LineMarkupBreakpointHit) != 0) {
if ((marker_mask & LineMarkupBreakpointHit) == 0)
new_marker_mask |= LineMarkupBreakpointHit;
}
if ((markup & mforms::LineMarkupCurrent) != 0) {
if ((marker_mask & LineMarkupCurrent) == 0)
new_marker_mask |= LineMarkupCurrent;
}
if (new_marker_mask != 0)
_code_editor_impl->send_editor(this, SCI_MARKERADDSET, line, new_marker_mask);
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditor::remove_markup(LineMarkup markup, ssize_t line) {
if (markup == mforms::LineMarkupAll || line < 0) {
if (line < 0)
_code_editor_impl->send_editor(this, SCI_MARKERDELETEALL, -1, 0);
else
_code_editor_impl->send_editor(this, SCI_MARKERDELETE, line, -1);
} else {
if ((markup & mforms::LineMarkupStatement) != 0)
_code_editor_impl->send_editor(this, SCI_MARKERDELETE, line, CE_STATEMENT_MARKER);
if ((markup & mforms::LineMarkupError) != 0)
_code_editor_impl->send_editor(this, SCI_MARKERDELETE, line, CE_ERROR_MARKER);
if ((markup & mforms::LineMarkupErrorContinue) != 0)
_code_editor_impl->send_editor(this, SCI_MARKERDELETE, line, CE_ERROR_CONTINUE_MARKER);
if ((markup & mforms::LineMarkupBreakpoint) != 0)
_code_editor_impl->send_editor(this, SCI_MARKERDELETE, line, CE_BREAKPOINT_MARKER);
if ((markup & mforms::LineMarkupBreakpointHit) != 0)
_code_editor_impl->send_editor(this, SCI_MARKERDELETE, line, CE_BREAKPOINT_HIT_MARKER);
if ((markup & mforms::LineMarkupCurrent) != 0)
_code_editor_impl->send_editor(this, SCI_MARKERDELETE, line, CE_CURRENT_LINE_MARKER);
}
}
//----------------------------------------------------------------------------------------------------------------------
bool CodeEditor::has_markup(LineMarkup markup, size_t line) {
sptr_t markers = _code_editor_impl->send_editor(this, SCI_MARKERGET, line, 0);
if ((markup & markers) != 0)
return true;
return false;
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditor::show_indicator(RangeIndicator indicator, size_t start, size_t length) {
// Scintilla supports a model that not only sets an indicator in a given range but additionally
// assigns a value to this indicator. This is to allow drawing an indicator with different styles.
// However, currently all values are drawn in the same style and it is neither clear if that will ever
// change nor what the actual use is, given that you always can set different indicators for different styles.
// Unfortunately, you cannot query an indicator as such, only its value, so we have to set a value too
// even if that is quite useless.
switch (indicator) {
case mforms::RangeIndicatorError:
_code_editor_impl->send_editor(this, SCI_SETINDICATORCURRENT, ERROR_INDICATOR, 0);
_code_editor_impl->send_editor(this, SCI_SETINDICATORVALUE, ERROR_INDICATOR_VALUE, 0);
_code_editor_impl->send_editor(this, SCI_INDICATORFILLRANGE, start, length);
break;
case mforms::RangeIndicatorNone: // No effect here. Only used to have "none" value for indicator tests.
break;
}
}
//----------------------------------------------------------------------------------------------------------------------
RangeIndicator CodeEditor::indicator_at(size_t position) {
sptr_t result = _code_editor_impl->send_editor(this, SCI_INDICATORVALUEAT, ERROR_INDICATOR, position);
if (result == ERROR_INDICATOR_VALUE)
return mforms::RangeIndicatorError;
return mforms::RangeIndicatorNone;
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditor::remove_indicator(RangeIndicator indicator, size_t start, size_t length) {
switch (indicator) {
case mforms::RangeIndicatorError:
_code_editor_impl->send_editor(this, SCI_SETINDICATORCURRENT, ERROR_INDICATOR, 0);
_code_editor_impl->send_editor(this, SCI_INDICATORCLEARRANGE, start, length);
break;
case mforms::RangeIndicatorNone: // No effect here. Only needed to have a "none" value for indicator tests.
break;
}
}
//----------------------------------------------------------------------------------------------------------------------
size_t mforms::CodeEditor::line_count() {
return _code_editor_impl->send_editor(this, SCI_GETLINECOUNT, 0, 0);
}
//----------------------------------------------------------------------------------------------------------------------
size_t CodeEditor::text_length() {
return _code_editor_impl->send_editor(this, SCI_GETLENGTH, 0, 0);
}
//----------------------------------------------------------------------------------------------------------------------
size_t CodeEditor::position_from_line(size_t line_number) {
return _code_editor_impl->send_editor(this, SCI_POSITIONFROMLINE, line_number, 0);
}
//----------------------------------------------------------------------------------------------------------------------
size_t CodeEditor::line_from_position(size_t position) {
return _code_editor_impl->send_editor(this, SCI_LINEFROMPOSITION, position, 0);
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditor::set_font(const std::string& fontDescription) {
// Set this font for all styles.
std::string font;
float size;
bool bold;
bool italic;
// Bold and italic are ignored as they can be set by a highlighter style.
if (base::parse_font_description(fontDescription, font, size, bold, italic)) {
#if !defined(_MSC_VER) && !defined(__APPLE__)
// Scintilla requires the ! in front of the font name to interpret it as a pango/fontconfig font.
if (!font.empty() && font[0] != '!')
font = "!" + font;
#endif
for (int i = 0; i < 128; i++) {
_code_editor_impl->send_editor(this, SCI_STYLESETFONT, i, (sptr_t)font.c_str());
_code_editor_impl->send_editor(this, SCI_STYLESETSIZE, i, (sptr_t)size);
}
}
// Recompute the line number margin width if it is visible.
sptr_t lineNumberStyleWidth = _code_editor_impl->send_editor(this, SCI_GETMARGINWIDTHN, 0, 0);
if (lineNumberStyleWidth > 0) {
lineNumberStyleWidth = _code_editor_impl->send_editor(this, SCI_TEXTWIDTH, STYLE_LINENUMBER, (sptr_t) "_9999");
_code_editor_impl->send_editor(this, SCI_SETMARGINWIDTHN, 0, lineNumberStyleWidth);
}
}
//----------------------------------------------------------------------------------------------------------------------
/**
* Converts scintilla key modifier codes to mforms codes.
*/
mforms::ModifierKey getModifiers(int scintilla_modifiers) {
mforms::ModifierKey modifiers = mforms::ModifierNoModifier;
if ((scintilla_modifiers & SCMOD_CTRL) == SCMOD_CTRL)
modifiers = modifiers | mforms::ModifierControl;
if ((scintilla_modifiers & SCMOD_ALT) == SCMOD_ALT)
modifiers = modifiers | mforms::ModifierAlt;
if ((scintilla_modifiers & SCMOD_SHIFT) == SCMOD_SHIFT)
modifiers = modifiers | mforms::ModifierShift;
return modifiers;
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditor::on_notify(SCNotification* notification) {
switch (notification->nmhdr.code) {
case SCN_MARGINCLICK: {
sptr_t line = _code_editor_impl->send_editor(this, SCI_LINEFROMPOSITION, notification->position, 0);
#ifndef __APPLE__
// This handling is already implemented in ScintillaView.mm.
if (notification->margin == 2) {
// A click on the folder margin. Toggle the current line if possible.
_code_editor_impl->send_editor(this, SCI_TOGGLEFOLD, line, 0);
}
#endif
mforms::ModifierKey modifiers = getModifiers(notification->modifiers);
_gutter_clicked_event(notification->margin, (int)line, modifiers);
break;
}
case SCN_MODIFIED: {
// Check for markers that would get removed.
// Usually doesn't come with any of the other notification types.
if ((notification->modificationType & SC_MOD_BEFOREDELETE) != 0)
handleMarkerDeletion(notification->position, notification->length);
// Text insertion or removal.
if ((notification->modificationType & (SC_MOD_INSERTTEXT | SC_MOD_DELETETEXT)) != 0) {
handleMarkerMove(notification->position, notification->linesAdded);
_change_event(notification->position, notification->length, notification->linesAdded,
(notification->modificationType & SC_MOD_INSERTTEXT) != 0);
}
break;
}
case SCN_AUTOCSELECTION:
_auto_completion_event(mforms::AutoCompletionSelection, notification->position, notification->text);
break;
case SCN_AUTOCCANCELLED:
_auto_completion_event(mforms::AutoCompletionCancelled, 0, "");
break;
case SCN_AUTOCCHARDELETED:
_auto_completion_event(mforms::AutoCompletionCharDeleted, 0, "");
break;
case SCN_DWELLSTART:
_dwell_event(true, notification->position, notification->x, notification->y);
break;
case SCN_DWELLEND:
_dwell_event(false, 0, 0, 0);
break;
case SCN_UPDATEUI:
switch (notification->updated) {
case SC_UPDATE_CONTENT: // Contents, styling or markers have been changed.
break;
case SC_UPDATE_SELECTION: // Selection has been changed or the caret moved.
updateBraceHighlighting();
NotificationCenter::get()->send("GNTextSelectionChanged", this);
break;
case SC_UPDATE_V_SCROLL: // Scrolled vertically.
break;
case SC_UPDATE_H_SCROLL: // Scrolled horizontally.
break;
}
break;
case SCN_CHARADDED:
_char_added_event(notification->ch);
if (_auto_indent && notification->ch == '\n') {
sptr_t pos = _code_editor_impl->send_editor(this, SCI_GETCURRENTPOS, 0, 0);
sptr_t line = _code_editor_impl->send_editor(this, SCI_LINEFROMPOSITION, pos, 0);
if (line > 0) {
sptr_t indentation = _code_editor_impl->send_editor(this, SCI_GETLINEINDENTATION, line - 1, 0);
if (indentation > 0) {
// Switch off tabs for a moment. We don't want a mix of tabs and spaces auto inserted
// and tabs mess up the new indentation.
sptr_t use_tabs = _code_editor_impl->send_editor(this, SCI_GETUSETABS, 0, 0);
_code_editor_impl->send_editor(this, SCI_SETUSETABS, 0, 0);
_code_editor_impl->send_editor(this, SCI_SETLINEINDENTATION, line, indentation);
_code_editor_impl->send_editor(this, SCI_GOTOPOS, pos + indentation, 0);
_code_editor_impl->send_editor(this, SCI_SETUSETABS, use_tabs, 0);
}
}
}
break;
case SCN_FOCUSIN:
focus_changed();
break;
case SCN_FOCUSOUT:
_signal_lost_focus(); // For validation, parsing etc.
break;
}
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditor::on_command(int command) {
// TODO: removal candidate.
}
//----------------------------------------------------------------------------------------------------------------------
bool CodeEditor::key_event(KeyCode code, ModifierKey modifier, const std::string& text) {
// Return true if the key event can be further processed by the sender.
// Return false if it is handled in backend code.
if (_key_event_signal.empty())
return true;
return *_key_event_signal(code, modifier, text);
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditor::set_features(CodeEditorFeature features, bool flag) {
if ((features & mforms::FeatureWrapText) != 0) {
if (flag)
_code_editor_impl->send_editor(this, SCI_SETWRAPMODE, SC_WRAP_WORD, 0);
else
_code_editor_impl->send_editor(this, SCI_SETWRAPMODE, SC_WRAP_NONE, 0);
}
if ((features & mforms::FeatureGutter) != 0) {
if (flag) {
sptr_t width = _code_editor_impl->send_editor(this, SCI_TEXTWIDTH, STYLE_LINENUMBER, (sptr_t) "_9999");
_code_editor_impl->send_editor(this, SCI_SETMARGINWIDTHN, 0, width); // line numbers
_code_editor_impl->send_editor(this, SCI_SETMARGINWIDTHN, 1, 16); // markers
_code_editor_impl->send_editor(this, SCI_SETMARGINWIDTHN, 2, 16); // fold markers
} else {
_code_editor_impl->send_editor(this, SCI_SETMARGINWIDTHN, 0, 0);
_code_editor_impl->send_editor(this, SCI_SETMARGINWIDTHN, 1, 0);
_code_editor_impl->send_editor(this, SCI_SETMARGINWIDTHN, 2, 0);
}
}
if ((features & mforms::FeatureReadOnly) != 0)
_code_editor_impl->send_editor(this, SCI_SETREADONLY, flag, 0);
if ((features & mforms::FeatureShowSpecial) != 0) {
_code_editor_impl->send_editor(this, SCI_SETVIEWEOL, flag, 0);
if (flag)
_code_editor_impl->send_editor(this, SCI_SETVIEWWS, SCWS_VISIBLEALWAYS, 0);
else
_code_editor_impl->send_editor(this, SCI_SETVIEWWS, SCWS_INVISIBLE, 0);
}
if ((features & mforms::FeatureUsePopup) != 0)
_code_editor_impl->send_editor(this, SCI_USEPOPUP, flag, 0);
if ((features & mforms::FeatureConvertEolOnPaste) != 0)
_code_editor_impl->send_editor(this, SCI_SETPASTECONVERTENDINGS, flag, 0);
if ((features & mforms::FeatureScrollOnResize) != 0)
_scroll_on_resize = true;
if ((features & mforms::FeatureFolding) != 0)
_code_editor_impl->send_editor(this, SCI_SETPROPERTY, (uptr_t) "fold", flag ? (sptr_t) "1" : (sptr_t) "0");
if ((features & mforms::FeatureAutoIndent) != 0)
_auto_indent = true;
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditor::toggle_features(CodeEditorFeature features) {
// Toggling a feature involves querying its current state which is sometimes not possible with a
// single value, so instead of returning the current state and let the application call
// set_features we do it internally with this toggle_features function.
if ((features & mforms::FeatureWrapText) != 0) {
if (_code_editor_impl->send_editor(this, SCI_GETWRAPMODE, 0, 0) == SC_WRAP_NONE)
_code_editor_impl->send_editor(this, SCI_SETWRAPMODE, SC_WRAP_WORD, 0);
else
_code_editor_impl->send_editor(this, SCI_SETWRAPMODE, SC_WRAP_NONE, 0);
}
if ((features & mforms::FeatureGutter) != 0)
set_features(mforms::FeatureGutter, _code_editor_impl->send_editor(this, SCI_GETMARGINWIDTHN, 0, 0) == 0);
if ((features & mforms::FeatureReadOnly) != 0)
set_features(mforms::FeatureReadOnly, _code_editor_impl->send_editor(this, SCI_GETREADONLY, 0, 0) == 0);
if ((features & mforms::FeatureShowSpecial) != 0)
set_features(mforms::FeatureShowSpecial, _code_editor_impl->send_editor(this, SCI_GETVIEWEOL, 0, 0) == 0);
// if ((features & mforms::FeatureUsePopup) != 0)
// Not searchable so we cannot toggle it.
if ((features & mforms::FeatureConvertEolOnPaste) != 0)
set_features(mforms::FeatureConvertEolOnPaste,
_code_editor_impl->send_editor(this, SCI_GETPASTECONVERTENDINGS, 0, 0) != 0);
if ((features & mforms::FeatureScrollOnResize) != 0)
_scroll_on_resize = !_scroll_on_resize;
if ((features & mforms::FeatureFolding) != 0) {
bool folding_enabled = _code_editor_impl->send_editor(this, SCI_GETPROPERTYINT, (uptr_t) "fold", 0) != 0;
_code_editor_impl->send_editor(this, SCI_SETPROPERTY, (uptr_t) "fold",
folding_enabled ? (sptr_t) "0" : (sptr_t) "1");
}
if ((features & mforms::FeatureAutoIndent) != 0)
_auto_indent = !_auto_indent;
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditor::set_read_only(bool flag) {
_code_editor_impl->send_editor(this, SCI_SETREADONLY, flag, 0);
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditor::reset_undo_stack() {
_code_editor_impl->send_editor(this, SCI_EMPTYUNDOBUFFER, 0, 0);
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditor::reset_dirty() {
_code_editor_impl->send_editor(this, SCI_SETSAVEPOINT, 0, 0);
}
//----------------------------------------------------------------------------------------------------------------------
bool CodeEditor::is_dirty() {
return _code_editor_impl->send_editor(this, SCI_GETMODIFY, 0, 0) != 0;
}
//----------------------------------------------------------------------------------------------------------------------
size_t CodeEditor::get_caret_pos() {
return _code_editor_impl->send_editor(this, SCI_GETCURRENTPOS, 0, 0);
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditor::set_caret_pos(size_t position) {
_code_editor_impl->send_editor(this, SCI_GOTOPOS, position, 0);
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditor::get_line_column_pos(size_t position, size_t& line, size_t& column) {
line = _code_editor_impl->send_editor(this, SCI_LINEFROMPOSITION, position, 0);
column = _code_editor_impl->send_editor(this, SCI_GETCOLUMN, position, 0);
;
}
//----------------------------------------------------------------------------------------------------------------------
bool CodeEditor::can_undo() {
return _code_editor_impl->send_editor(this, SCI_CANUNDO, 0, 0) != 0;
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditor::undo() {
_code_editor_impl->send_editor(this, SCI_UNDO, 0, 0);
}
//----------------------------------------------------------------------------------------------------------------------
bool CodeEditor::can_redo() {
return _code_editor_impl->send_editor(this, SCI_CANREDO, 0, 0) != 0;
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditor::redo() {
_code_editor_impl->send_editor(this, SCI_REDO, 0, 0);
}
//----------------------------------------------------------------------------------------------------------------------
bool CodeEditor::can_cut() {
return can_copy() && can_delete();
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditor::cut() {
_code_editor_impl->send_editor(this, SCI_CUT, 0, 0);
}
//----------------------------------------------------------------------------------------------------------------------
bool CodeEditor::can_copy() {
sptr_t length = _code_editor_impl->send_editor(this, SCI_GETSELECTIONEND, 0, 0) -
_code_editor_impl->send_editor(this, SCI_GETSELECTIONSTART, 0, 0);
return length > 0;
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditor::copy() {
_code_editor_impl->send_editor(this, SCI_COPY, 0, 0);
}
//----------------------------------------------------------------------------------------------------------------------
bool CodeEditor::can_paste() {
return _code_editor_impl->send_editor(this, SCI_CANPASTE, 0, 0) != 0;
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditor::paste() {
_code_editor_impl->send_editor(this, SCI_PASTE, 0, 0);
}
//----------------------------------------------------------------------------------------------------------------------
bool CodeEditor::can_delete() {
return can_copy() && _code_editor_impl->send_editor(this, SCI_GETREADONLY, 0, 0) == 0;
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditor::do_delete() {
replace_selected_text("");
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditor::select_all() {
_code_editor_impl->send_editor(this, SCI_SELECTALL, 0, 0);
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditor::set_status_text(const std::string& text) {
// Optional implementation.
if (_code_editor_impl->set_status_text != NULL)
_code_editor_impl->set_status_text(this, text);
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditor::show_find_panel(bool replace) {
if (_find_panel == NULL)
_find_panel = new FindPanel(this);
_find_panel->enable_replace(replace);
if (_show_find_panel)
_show_find_panel(this, true);
_find_panel->focus();
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditor::hide_find_panel() {
if (_find_panel == NULL)
return;
if (_show_find_panel && _find_panel->is_shown())
_show_find_panel(this, false);
focus();
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditor::set_show_find_panel_callback(std::function<void(CodeEditor*, bool)> callback) {
_show_find_panel = callback;
}
//----------------------------------------------------------------------------------------------------------------------
bool CodeEditor::find_and_highlight_text(const std::string& search_text, FindFlags flags, bool scroll_to,
bool backwards) {
if (search_text.size() == 0)
return false;
bool wrap = (flags & FindWrapAround) != 0;
int search_flags = 0;
if (flags & FindMatchCase)
search_flags |= SCFIND_MATCHCASE;
if (flags & FindWholeWords)
search_flags |= SCFIND_WHOLEWORD;
if (flags & FindRegex)
search_flags |= SCFIND_REGEXP;
sptr_t selection_start = _code_editor_impl->send_editor(this, SCI_GETSELECTIONSTART, 0, 0);
sptr_t selection_end = _code_editor_impl->send_editor(this, SCI_GETSELECTIONEND, 0, 0);
// Sets the start point for the upcoming search to the begin of the current selection.
// For forward searches we have therefore to set the selection start to the current selection end
// for proper incremental search. This does not harm as we either get a new selection if something
// is found or the previous selection is restored.
if (!backwards)
_code_editor_impl->send_editor(this, SCI_SETSELECTIONSTART, selection_end, 0);
_code_editor_impl->send_editor(this, SCI_SEARCHANCHOR, 0, 0);
sptr_t result = 0;
// The following call will also set the selection if something was found.
if (backwards) {
result = _code_editor_impl->send_editor(this, SCI_SEARCHPREV, search_flags, (sptr_t)search_text.c_str());
if (result < 0 && wrap) {
// Try again from the end of the document if nothing could be found so far and
// wrapped search is set.
_code_editor_impl->send_editor(this, SCI_SETSELECTIONSTART,
_code_editor_impl->send_editor(this, SCI_GETTEXTLENGTH, 0, 0), 0);
_code_editor_impl->send_editor(this, SCI_SEARCHANCHOR, 0, 0);
result = _code_editor_impl->send_editor(this, SCI_SEARCHNEXT, search_flags, (sptr_t)search_text.c_str());
}
} else {
result = _code_editor_impl->send_editor(this, SCI_SEARCHNEXT, search_flags, (sptr_t)search_text.c_str());
if (result < 0 && wrap) {
// Try again from the start of the document if nothing could be found so far and
// wrapped search is set.
_code_editor_impl->send_editor(this, SCI_SETSELECTIONSTART, 0, 0);
_code_editor_impl->send_editor(this, SCI_SEARCHANCHOR, 0, 0);
result = _code_editor_impl->send_editor(this, SCI_SEARCHNEXT, search_flags, (sptr_t)search_text.c_str());
}
}
if (result >= 0) {
if (scroll_to)
_code_editor_impl->send_editor(this, SCI_SCROLLCARET, 0, 0);
} else {
// Restore the former selection if we did not find anything.
_code_editor_impl->send_editor(this, SCI_SETSELECTIONSTART, selection_start, 0);
_code_editor_impl->send_editor(this, SCI_SETSELECTIONEND, selection_end, 0);
}
return (result >= 0) ? true : false;
}
//----------------------------------------------------------------------------------------------------------------------
/**
* Searches the given text and replaces it by new_text. Returns the number of replacements performed.
*/
size_t CodeEditor::find_and_replace_text(const std::string& search_text, const std::string& new_text, FindFlags flags,
bool do_all) {
if (search_text.size() == 0)
return 0;
// The current position is where we start searching for single occurrences. Otherwise we start at
// the beginning of the document.
sptr_t start_position;
if (do_all)
start_position = 0;
else
start_position = _code_editor_impl->send_editor(this, SCI_GETCURRENTPOS, 0, 0);
sptr_t endPosition = _code_editor_impl->send_editor(this, SCI_GETTEXTLENGTH, 0, 0);
int search_flags = 0;
if (flags & FindMatchCase)
search_flags |= SCFIND_MATCHCASE;
if (flags & FindWholeWords)
search_flags |= SCFIND_WHOLEWORD;
if (flags & FindRegex)
search_flags |= SCFIND_REGEXP;
_code_editor_impl->send_editor(this, SCI_SETSEARCHFLAGS, search_flags, 0);
_code_editor_impl->send_editor(this, SCI_SETTARGETSTART, start_position, 0);
_code_editor_impl->send_editor(this, SCI_SETTARGETEND, endPosition, 0);
sptr_t result;
size_t replace_count = 0;
if (do_all) {
while (true) {
result =
_code_editor_impl->send_editor(this, SCI_SEARCHINTARGET, search_text.size(), (sptr_t)search_text.c_str());
if (result < 0)
break;
replace_count++;
/*result =*/_code_editor_impl->send_editor(this, SCI_REPLACETARGET, new_text.size(), (sptr_t)new_text.c_str());
// The replacement changes the target range to the replaced text. Continue after that until the end.
// The text length might be changed by the replacement so make sure the target end is the actual
// text end.
_code_editor_impl->send_editor(this, SCI_SETTARGETSTART,
_code_editor_impl->send_editor(this, SCI_GETTARGETEND, 0, 0), 0);
_code_editor_impl->send_editor(this, SCI_SETTARGETEND,
_code_editor_impl->send_editor(this, SCI_GETTEXTLENGTH, 0, 0), 0);
}
} else {
result = _code_editor_impl->send_editor(this, SCI_SEARCHINTARGET, search_text.size(), (sptr_t)search_text.c_str());
replace_count = (result < 0) ? 0 : 1;
if (replace_count > 0) {
/*result =*/_code_editor_impl->send_editor(this, SCI_REPLACETARGET, new_text.size(), (sptr_t)new_text.c_str());
// For a single replace we set the new selection to the replaced text.
_code_editor_impl->send_editor(this, SCI_SETSELECTIONSTART,
_code_editor_impl->send_editor(this, SCI_GETTARGETSTART, 0, 0), 0);
_code_editor_impl->send_editor(this, SCI_SETSELECTIONEND,
_code_editor_impl->send_editor(this, SCI_GETTARGETEND, 0, 0), 0);
}
}
return replace_count;
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditor::jump_to_next_placeholder() {
sptr_t current_pos = _code_editor_impl->send_editor(this, SCI_GETCURRENTPOS, 0, 0);
sptr_t text_size = _code_editor_impl->send_editor(this, SCI_GETLENGTH, 0, 0);
Sci_TextToFind what;
what.lpstrText = (char*)"<{";
what.chrg.cpMin = (long)current_pos;
what.chrg.cpMax = (long)text_size;
sptr_t result = _code_editor_impl->send_editor(this, SCI_FINDTEXT, 0, (sptr_t)&what);
bool found = false;
if (result >= 0) {
static const int max_placeholder_length = 256;
what.lpstrText = (char*)"}>";
what.chrg.cpMin = (long)result;
what.chrg.cpMax = what.chrg.cpMin + max_placeholder_length; // arbitrary max size for placeholder text
result = _code_editor_impl->send_editor(this, SCI_FINDTEXT, 0, (sptr_t)&what);
if (result >= 0) {
char buffer[max_placeholder_length];
Sci_TextRange tr;
tr.chrg.cpMin = what.chrg.cpMin;
tr.chrg.cpMax = (long)result + 2;
tr.lpstrText = buffer;
_code_editor_impl->send_editor(this, SCI_GETTEXTRANGE, 0, (sptr_t)&tr);
if (!memchr(buffer, '\n', tr.chrg.cpMax - tr.chrg.cpMin)) {
// jump to placeholder and select it
_code_editor_impl->send_editor(this, SCI_SETSELECTIONSTART, tr.chrg.cpMin, 0);
_code_editor_impl->send_editor(this, SCI_SETSELECTIONEND, tr.chrg.cpMax, 0);
_code_editor_impl->send_editor(this, SCI_SCROLLCARET, 0, 0);
found = true;
}
}
}
if (!found) {
// restore the cursor to where it was
_code_editor_impl->send_editor(this, SCI_SETSELECTIONSTART, current_pos, 0);
_code_editor_impl->send_editor(this, SCI_SETSELECTIONEND, current_pos, 0);
}
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditor::auto_completion_show(size_t chars_entered, const std::vector<std::pair<int, std::string> >& entries) {
if (entries.size() == 0)
return;
std::stringstream list;
for (size_t i = 0; i < entries.size(); ++i) {
if (i > 0)
list << AC_LIST_SEPARATOR;
list << entries[i].second;
if (entries[i].first > -1)
list << AC_TYPE_SEPARATOR << entries[i].first;
}
_code_editor_impl->send_editor(this, SCI_AUTOCSHOW, chars_entered, (sptr_t)list.str().c_str());
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditor::auto_completion_show(size_t chars_entered, const std::vector<std::string>& entries) {
std::stringstream list;
for (size_t i = 0; i < entries.size(); ++i) {
if (i > 0)
list << AC_LIST_SEPARATOR;
list << entries[i];
}
_code_editor_impl->send_editor(this, SCI_AUTOCSHOW, chars_entered, (sptr_t)list.str().c_str());
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditor::auto_completion_cancel() {
_code_editor_impl->send_editor(this, SCI_AUTOCCANCEL, 0, 0);
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditor::auto_completion_options(bool ignore_case, bool choose_single, bool auto_hide, bool drop_rest_of_word,
bool cancel_at_start) {
_code_editor_impl->send_editor(this, SCI_AUTOCSETIGNORECASE, ignore_case, 0);
_code_editor_impl->send_editor(this, SCI_AUTOCSETCHOOSESINGLE, choose_single, 0);
_code_editor_impl->send_editor(this, SCI_AUTOCSETAUTOHIDE, auto_hide, 0);
_code_editor_impl->send_editor(this, SCI_AUTOCSETDROPRESTOFWORD, drop_rest_of_word, 0);
_code_editor_impl->send_editor(this, SCI_AUTOCSETCANCELATSTART, cancel_at_start, 0);
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditor::auto_completion_max_size(int width, int height) {
_code_editor_impl->send_editor(this, SCI_AUTOCSETMAXHEIGHT, height, 0);
_code_editor_impl->send_editor(this, SCI_AUTOCSETMAXWIDTH, width, 0);
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditor::auto_completion_register_images(const std::vector<std::pair<int, std::string> > &images) {
for (auto &image : images) {
if (ensureImage(image.second)) {
auto &data = registeredImages[image.second];
_code_editor_impl->send_editor(this, SCI_RGBAIMAGESETWIDTH, data.width, 0);
_code_editor_impl->send_editor(this, SCI_RGBAIMAGESETHEIGHT, data.height, 0);
// Scale factors are not supported for code completion images.
//_code_editor_impl->send_editor(this, SCI_RGBAIMAGESETSCALE, data.hiresImage ? 200 : 100, 0);
_code_editor_impl->send_editor(this, SCI_REGISTERRGBAIMAGE, image.first, reinterpret_cast<sptr_t>(data.data));
}
}
}
//----------------------------------------------------------------------------------------------------------------------
bool CodeEditor::auto_completion_active() {
return _code_editor_impl->send_editor(this, SCI_AUTOCACTIVE, 0, 0) != 0;
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditor::auto_completion_stops(const std::string& stops) {
_code_editor_impl->send_editor(this, SCI_AUTOCSTOPS, 0, (sptr_t)stops.c_str());
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditor::auto_completion_fillups(const std::string& fillups) {
_code_editor_impl->send_editor(this, SCI_AUTOCSETFILLUPS, 0, (sptr_t)fillups.c_str());
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditor::show_calltip(bool show, size_t position, const std::string& value) {
if (show)
_code_editor_impl->send_editor(this, SCI_CALLTIPSHOW, position, (sptr_t)value.c_str());
else
_code_editor_impl->send_editor(this, SCI_CALLTIPCANCEL, 0, 0);
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditor::set_eol_mode(mforms::EndOfLineMode mode, bool convert) {
_code_editor_impl->send_editor(this, SCI_SETEOLMODE, mode, 0);
if (convert)
_code_editor_impl->send_editor(this, SCI_CONVERTEOLS, mode, 0);
}
//----------------------------------------------------------------------------------------------------------------------
sptr_t CodeEditor::send_editor(unsigned int message, uptr_t wParam, sptr_t lParam) {
return _code_editor_impl->send_editor(this, message, wParam, lParam);
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditor::lost_focus() {
_signal_lost_focus();
}
//----------------------------------------------------------------------------------------------------------------------
void CodeEditor::resize() {
if (_scroll_on_resize)
_code_editor_impl->send_editor(this, SCI_SCROLLCARET, 0, 0);
}
//----------------------------------------------------------------------------------------------------------------------