Permalink
Switch branches/tags
Nothing to show
Find file
Fetching contributors…
Cannot retrieve contributors at this time
10346 lines (8560 sloc) 291 KB
/*******************************************************************************
*
* Copyright (C) 2009, Alexander Stigsen, e-texteditor.com
*
* This software is licensed under the Open Company License as described
* in the file license.txt, which you should have received as part of this
* distribution. The terms are also available at http://opencompany.org/license.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
******************************************************************************/
#include "EditorCtrl.h"
#include <wx/clipbrd.h>
#include <wx/filename.h>
#include <wx/tipwin.h>
#include <wx/file.h>
#include <set>
#include <algorithm>
#include "pcre.h"
#include "doc_byte_iter.h"
#include "tm_syntaxhandler.h"
#include "EditorFrame.h"
#include "StyleRun.h"
#include "FindCmdDlg.h"
#include "RunCmdDlg.h"
#include "BundleMenu.h"
#include "GutterCtrl.h"
#include "PreviewDlg.h"
#include "RedoDlg.h"
#include "CompletionPopup.h"
#include "MultilineDataObject.h"
#include "eDocumentPath.h"
#include "ShellRunner.h"
#include "Env.h"
#include "Fold.h"
#include "TextTip.h"
#include "RemoteThread.h"
#include "LiveCaret.h"
#include "eSettings.h"
#include "IAppPaths.h"
#include "Strings.h"
#include "ReplaceStringParser.h"
#include "Accelerators.h"
// Document Icons
#include "document.xpm"
// Generates a string appropriate for a Drag Command's TM_MODIFIER_FLAGS
// Note that we remap Windows modifier keys to the equivalents expected by TextMate commands.
static wxString get_modifier_key_env() {
wxString modifiers;
if (wxGetKeyState(WXK_SHIFT))
modifiers += wxT("SHIFT");
if (wxGetKeyState(WXK_ALT)) {
if (!modifiers.empty()) modifiers += wxT('|');
modifiers += wxT("CONTROL");
}
if (wxGetKeyState(WXK_WINDOWS_LEFT) || wxGetKeyState(WXK_WINDOWS_RIGHT)) {
if (!modifiers.empty()) modifiers += wxT('|');
modifiers += wxT("OPTION");
}
if (wxGetKeyState(WXK_CONTROL)) {
if (!modifiers.empty()) modifiers += wxT('|');
modifiers += wxT("COMMAND");
}
return modifiers;
}
// Embedded class: Sort list based on bundle
class CompareActionBundle : public binary_function<size_t, size_t, bool> {
public:
CompareActionBundle(const vector<const tmAction*>& actionList) : m_list(actionList) {};
bool operator() (const size_t a1, const size_t a2) const {
return m_list[a1]->bundle > m_list[a2]->bundle;
}
private:
const vector<const tmAction*>& m_list;
};
class DragDropTarget : public wxDropTarget {
public:
DragDropTarget(EditorCtrl& parent);
wxDragResult OnData(wxCoord x, wxCoord y, wxDragResult def);
wxDragResult OnDragOver(wxCoord x, wxCoord y, wxDragResult def) {
m_parent.OnDragOver(x, y);
return def;
}
private:
EditorCtrl& m_parent;
wxFileDataObject* m_fileObject;
wxTextDataObject* m_textObject;
MultilineDataObject* m_columnObject;
wxDataObjectComposite* m_dataObject;
};
enum ShellOutput {soDISCARD, soREPLACESEL, soREPLACEDOC, soINSERT, soSNIPPET, soHTML, soTOOLTIP, soNEWDOC};
enum EditorCtrl_IDs {
TIMER_FOLDTOOLTIP = 100,
ID_LEFTSCROLL
};
BEGIN_EVENT_TABLE(EditorCtrl, wxControl)
EVT_PAINT(EditorCtrl::OnPaint)
EVT_CHAR(EditorCtrl::OnChar)
EVT_KEY_DOWN(EditorCtrl::OnKeyDown)
EVT_KEY_UP(EditorCtrl::OnKeyUp)
EVT_SIZE(EditorCtrl::OnSize)
EVT_ERASE_BACKGROUND(EditorCtrl::OnEraseBackground)
EVT_LEFT_DOWN(EditorCtrl::OnMouseLeftDown)
EVT_RIGHT_DOWN(EditorCtrl::OnMouseRightDown)
EVT_LEFT_UP(EditorCtrl::OnMouseLeftUp)
EVT_LEFT_DCLICK(EditorCtrl::OnMouseDClick)
EVT_MOTION(EditorCtrl::OnMouseMotion)
EVT_MOUSE_CAPTURE_LOST(EditorCtrl::OnMouseCaptureLost)
EVT_ENTER_WINDOW(EditorCtrl::OnMouseMotion)
EVT_LEAVE_WINDOW(EditorCtrl::OnLeaveWindow)
EVT_MOUSEWHEEL(EditorCtrl::OnMouseWheel)
EVT_SCROLLWIN(EditorCtrl::OnScroll)
EVT_COMMAND_SCROLL(ID_LEFTSCROLL, EditorCtrl::OnScrollBar)
EVT_IDLE(EditorCtrl::OnIdle)
EVT_CLOSE(EditorCtrl::OnClose)
EVT_MENU_RANGE(1000, 1999, EditorCtrl::OnPopupListMenu)
EVT_TIMER(TIMER_FOLDTOOLTIP, EditorCtrl::OnFoldTooltipTimer)
EVT_SET_FOCUS(EditorCtrl::OnFocus)
END_EVENT_TABLE()
// Initialize statics
const unsigned int EditorCtrl::m_caretWidth = 2;
unsigned long EditorCtrl::s_ctrlDownTime = 0;
bool EditorCtrl::s_altGrDown = false;
/// Open a page saved from a previous session
EditorCtrl::EditorCtrl(const int page_id, CatalystWrapper& cw, wxBitmap& bitmap, wxWindow* parent, EditorFrame& parentFrame) :
m_catalyst(cw),
m_doc(cw),
dispatcher(cw.GetDispatcher()),
bitmap(bitmap),
m_parentFrame(parentFrame),
m_syntaxHandler(m_parentFrame.GetSyntaxHandler()),
m_theme(m_syntaxHandler.GetTheme()),
m_lines(mdc, m_doc, *this, m_theme),
m_search_hl_styler(m_doc, m_lines, m_searchRanges, m_cursors, m_theme),
m_variable_hl_styler(m_doc, m_lines, m_searchRanges, m_cursors, m_theme, eGetSettings(), *this),
m_html_hl_styler(m_doc, m_lines, m_theme, eGetSettings(), *this),
m_syntaxstyler(m_doc, m_lines, &m_syntaxHandler),
m_selectionsStyler(m_doc, m_lines, m_theme, *this),
m_foldTooltipTimer(this, TIMER_FOLDTOOLTIP),
m_activeTooltip(NULL),
m_beforeRedrawCallback(NULL),
m_afterRedrawCallback(NULL),
m_scrollCallback(NULL),
m_enableDrawing(false),
m_isResizing(true),
scrollPos(0),
m_scrollPosX(0),
topline(-1),
commandMode(false),
m_changeToken(0),
m_savedForPreview(false),
lastpos(0),
m_currentSel(-1),
do_freeze(true),
m_options_cache(0),
m_re(NULL),
m_symbolCacheToken(0),
m_tabSettingsFromSyntax(false),
m_tabSettingsOverriden(false),
m_tabWidth(0),
m_softTabs(false),
bookmarks(m_lines),
m_commandHandler(parentFrame, *this),
m_snippetHandler(*this),
m_macro(parentFrame.GetMacro())
{
Create(parent, wxID_ANY, wxPoint(-100,-100), wxDefaultSize, wxNO_BORDER|wxWANTS_CHARS|wxCLIP_CHILDREN|wxNO_FULL_REPAINT_ON_RESIZE);
Hide(); // start hidden to avoid flicker
Init();
eFrameSettings& settings = parentFrame.GetFrameSettings();
RestoreSettings(page_id, settings);
}
/// Open a document
EditorCtrl::EditorCtrl(const doc_id di, const wxString& mirrorPath, CatalystWrapper& cw, wxBitmap& bitmap, wxWindow* parent, EditorFrame& parentFrame, const wxPoint& pos, const wxSize& size):
m_catalyst(cw),
m_doc(cw),
dispatcher(cw.GetDispatcher()),
bitmap(bitmap),
m_parentFrame(parentFrame),
m_syntaxHandler(m_parentFrame.GetSyntaxHandler()),
m_theme(m_syntaxHandler.GetTheme()),
m_lines(mdc, m_doc, *this, m_theme),
m_search_hl_styler(m_doc, m_lines, m_searchRanges, m_cursors, m_theme),
m_variable_hl_styler(m_doc, m_lines, m_searchRanges, m_cursors, m_theme, eGetSettings(), *this),
m_html_hl_styler(m_doc, m_lines, m_theme, eGetSettings(), *this),
m_syntaxstyler(m_doc, m_lines, &m_syntaxHandler),
m_selectionsStyler(m_doc, m_lines, m_theme, *this),
m_foldTooltipTimer(this, TIMER_FOLDTOOLTIP),
m_activeTooltip(NULL),
m_beforeRedrawCallback(NULL),
m_afterRedrawCallback(NULL),
m_scrollCallback(NULL),
m_enableDrawing(false),
m_isResizing(true),
scrollPos(0),
m_scrollPosX(0),
topline(-1),
commandMode(false),
m_changeToken(0),
m_savedForPreview(false),
lastpos(0),
m_currentSel(-1),
do_freeze(true),
m_options_cache(0),
m_re(NULL),
m_symbolCacheToken(0),
m_tabSettingsFromSyntax(false),
m_tabSettingsOverriden(false),
m_tabWidth(0),
m_softTabs(false),
bookmarks(m_lines),
m_commandHandler(parentFrame, *this),
m_snippetHandler(*this),
m_macro(parentFrame.GetMacro())
{
Create(parent, wxID_ANY, pos, size, wxNO_BORDER|wxWANTS_CHARS|wxCLIP_CHILDREN|wxNO_FULL_REPAINT_ON_RESIZE);
Init();
cxLOCKDOC_WRITE(m_doc)
// Set the document & settings
doc.SetDocument(di);
doc.SetDocRead();
cxENDLOCK
m_path = mirrorPath;
// Set lines & positions
m_lines.ReLoadText();
// Set the syntax to match the new document
m_syntaxstyler.UpdateSyntax();
SetTabWidthFromSyntax();
// Init Folding
FoldingClear();
}
/// Create a new empty document
EditorCtrl::EditorCtrl(CatalystWrapper& cw, wxBitmap& bitmap, wxWindow* parent, EditorFrame& parentFrame, const wxPoint& pos, const wxSize& size):
m_catalyst(cw),
m_doc(cw, true),
dispatcher(cw.GetDispatcher()),
bitmap(bitmap),
m_parentFrame(parentFrame),
m_syntaxHandler(m_parentFrame.GetSyntaxHandler()),
m_theme(m_syntaxHandler.GetTheme()),
m_lines(mdc, m_doc, *this, m_theme),
m_search_hl_styler(m_doc, m_lines, m_searchRanges, m_cursors, m_theme),
m_variable_hl_styler(m_doc, m_lines, m_searchRanges, m_cursors, m_theme, eGetSettings(), *this),
m_html_hl_styler(m_doc, m_lines, m_theme, eGetSettings(), *this),
m_syntaxstyler(m_doc, m_lines, &m_syntaxHandler),
m_selectionsStyler(m_doc, m_lines, m_theme, *this),
m_foldTooltipTimer(this, TIMER_FOLDTOOLTIP),
m_activeTooltip(NULL),
m_beforeRedrawCallback(NULL),
m_afterRedrawCallback(NULL),
m_scrollCallback(NULL),
m_enableDrawing(false),
m_isResizing(true),
scrollPos(0),
m_scrollPosX(0),
topline(-1),
commandMode(false),
m_changeToken(0),
m_savedForPreview(false),
lastpos(0),
m_currentSel(-1),
do_freeze(true),
m_options_cache(0),
m_re(NULL),
m_symbolCacheToken(0),
m_tabSettingsFromSyntax(false),
m_tabSettingsOverriden(false),
m_tabWidth(0),
m_softTabs(false),
bookmarks(m_lines),
m_commandHandler(parentFrame, *this),
m_snippetHandler(*this),
m_macro(parentFrame.GetMacro())
{
Create(parent, wxID_ANY, pos, size, wxNO_BORDER|wxWANTS_CHARS|wxCLIP_CHILDREN|wxNO_FULL_REPAINT_ON_RESIZE);
Hide(); // start hidden to avoid flicker
cxLOCKDOC_WRITE(m_doc)
// Make sure we start out with a single empty revision
doc.Freeze();
cxENDLOCK
Init();
// Init Folding
FoldingClear();
}
void EditorCtrl::RestoreSettings(unsigned int page_id, eFrameSettings& settings, unsigned int subid) {
wxString mirrorPath;
doc_id di;
int newpos;
int topline;
wxString syntax;
vector<unsigned int> folds;
vector<unsigned int> bookmarks;
// Retrieve the page info
wxASSERT(0 <= page_id && page_id < settings.GetPageCount());
settings.GetPageSettings(page_id, mirrorPath, di, newpos, topline, syntax, folds, bookmarks, (SubPage)subid);
if (eDocumentPath::IsRemotePath(mirrorPath)) {
// If the mirror points to a remote file, we have to download it first.
SetDocument(di, mirrorPath);
}
else {
const bool isBundleItem = eDocumentPath::IsBundlePath(mirrorPath);
if (isBundleItem) m_remotePath = mirrorPath;
else m_path = mirrorPath;
cxLOCKDOC_WRITE(m_doc)
// Set the document & settings
doc.SetDocument(di);
doc.SetDocRead();
cxENDLOCK
m_lines.ReLoadText();
}
// Set the syntax to match the new path
if (syntax.empty()) {
m_syntaxstyler.UpdateSyntax();
FoldingClear(); // Init Folding
}
else SetSyntax(syntax);
SetDocumentAndScrollPosition(newpos, topline);
// Fold lines that were folded in previous session
if (!folds.empty()) {
// We have to make sure all text is syntaxed and all fold markers found
m_syntaxstyler.ParseAll();
UpdateFolds();
for (vector<unsigned int>::const_iterator p = folds.begin(); p != folds.end(); ++p) {
const unsigned int line_id = *p;
const cxFold target(line_id);
vector<cxFold>::iterator f = lower_bound(m_folds.begin(), m_folds.end(), target);
if (f != m_folds.end() && f->line_id == line_id && f->type == cxFOLD_START)
Fold(*p);
}
}
// Set bookmarks
for (vector<unsigned int>::const_iterator p = bookmarks.begin(); p != bookmarks.end(); ++p)
AddBookmark(*p);
}
void EditorCtrl::Init() {
// Initialize the memoryDC for dubblebuffering
mdc.SetFont(m_theme.font);
// We do not yet call mdc.SelectObject(bitmap) since the bitmap
// is shared with the other EditCtrl's. We call it in Show() & OnSize().
m_remoteProfile = NULL;
// Column selection state
m_blockKeyState = BLOCKKEY_NONE;
m_selMode = SEL_NORMAL;
// Settings
bool autopair = true;
m_doAutoWrap = true;
m_wrapAtMargin = false;
bool doShowMargin = false;
int marginChars = 80;
eSettings& settings = eGetSettings();
settings.GetSettingBool(wxT("autoPair"), autopair);
settings.GetSettingBool(wxT("autoWrap"), m_doAutoWrap);
settings.GetSettingBool(wxT("showMargin"), doShowMargin);
settings.GetSettingBool(wxT("wrapMargin"), m_wrapAtMargin);
settings.GetSettingInt(wxT("marginChars"), marginChars);
m_autopair.Enable(autopair);
m_lastScopePos = -1; // scope selection
if (!doShowMargin) m_wrapAtMargin = false;
// Initialize gutter (line numbers)
m_gutterCtrl = new GutterCtrl(*this, wxID_ANY);
m_gutterCtrl->Hide();
m_showGutter = false;
m_gutterLeft = true; // left side is default
m_gutterWidth = 0;
SetShowGutter(m_parentFrame.IsGutterShown());
m_leftScrollbar = NULL; // default is using internal (right) scrollbar
m_leftScrollWidth = 0;
// To keep track of when we should freeze versions
lastpos = 0;
lastaction = ACTION_INSERT;
// Initialize tooltips
//m_revTooltip.Create(this);
// resize the bitmap used for doublebuffering
wxSize size = GetClientSize();
if (bitmap.GetWidth() < size.x || bitmap.GetHeight() < size.y)
bitmap = wxBitmap(size.x, size.y);
// Init the lines
m_lines.SetWordWrap(m_parentFrame.GetWrapMode());
m_lines.ShowIndent(m_parentFrame.IsIndentShown());
m_lines.Init();
//m_lines.AddStyler(m_usersStyler);
m_lines.AddStyler(m_syntaxstyler);
m_lines.AddStyler(m_search_hl_styler);
m_lines.AddStyler(m_variable_hl_styler);
m_lines.AddStyler(m_html_hl_styler);
m_lines.AddStyler(m_selectionsStyler);
// Set initial tabsize
if(!m_tabSettingsFromSyntax) {
SetTabWidth(m_parentFrame.GetTabWidth(), m_parentFrame.IsSoftTabs(), false);
}
// Should we show margin line?
if (!doShowMargin) marginChars = 0;
m_lines.ShowMargin(marginChars);
// Create a caret to indicate edit position
m_caretHeight = m_lines.GetLineHeight();
caret = new LiveCaret(this, m_caretWidth, m_caretHeight); // will be deleted by window on destroy
caret->Move(0, 0);
SetCaret(caret);
caret->Show();
SetCursor(wxCursor(wxCURSOR_IBEAM));
// Set drop target so files and text can be dragged from explorer to trigger drag commands
DragDropTarget* dropTarget = new DragDropTarget(*this);
SetDropTarget(dropTarget);
// Make sure we get notified if we should display another document
dispatcher.SubscribeC(wxT("WIN_SETDOCUMENT"), (CALL_BACK)OnSetDocument, this);
dispatcher.SubscribeC(wxT("DOC_COMMITED"), (CALL_BACK)OnDocCommited, this);
dispatcher.SubscribeC(wxT("THEME_CHANGED"), (CALL_BACK)OnThemeChanged, this);
dispatcher.SubscribeC(wxT("BUNDLES_RELOADED"), (CALL_BACK)OnBundlesReloaded, this);
dispatcher.SubscribeC(wxT("SETTINGS_CHANGED"), (CALL_BACK)OnSettingsChanged, this);
}
EditorCtrl::~EditorCtrl() {
// Make sure we no longer recieve any notifiers
dispatcher.UnSubscribe(wxT("WIN_SETDOCUMENT"), (CALL_BACK)OnSetDocument, this);
dispatcher.UnSubscribe(wxT("DOC_COMMITED"), (CALL_BACK)OnDocCommited, this);
dispatcher.UnSubscribe(wxT("THEME_CHANGED"), (CALL_BACK)OnThemeChanged, this);
dispatcher.UnSubscribe(wxT("BUNDLES_RELOADED"), (CALL_BACK)OnBundlesReloaded, this);
dispatcher.UnSubscribe(wxT("SETTINGS_CHANGED"), (CALL_BACK)OnSettingsChanged, this);
// Delete the document
//catalyst.DeleteDocument(revision.GetDocumentID());
cxLOCKDOC_WRITE(m_doc)
doc.Close();
cxENDLOCK
NotifyParentMate();
ClearRemoteInfo();
}
// Notify mate that we have finished editing document
void EditorCtrl::NotifyParentMate() {
#ifdef __WXMSW__
if (!m_mate.empty()) {
HWND hWndRecv = ::FindWindow(wxT("wxWindowClassNR"), m_mate);
if (hWndRecv) {
const wxString cmd = wxT("DONE");
const wxCharBuffer msg = cmd.mb_str(wxConvUTF8);
// Send WM_COPYDATA message
COPYDATASTRUCT cds;
cds.dwData = 0;
cds.cbData = (DWORD)(strlen(msg.data()) + 1);
cds.lpData = (void*)msg.data();
::SendMessage(hWndRecv, WM_COPYDATA, (WPARAM)NULL, (LPARAM)&cds );
}
}
#endif
}
void EditorCtrl::SaveSettings(unsigned int i, eFrameSettings& settings) {
SaveSettings(i, settings, 0);
}
void EditorCtrl::SaveSettings(unsigned int i, eFrameSettings& settings, unsigned int subid) {
const wxString& path = GetPath();
const doc_id di = GetDocID();
const int pos = GetPos();
const int topline = GetTopLine();
const wxString& syntax = GetSyntaxName();
const vector<unsigned int> folds = GetFoldedLines();
settings.SetPageSettings(i, path, di, pos, topline, syntax, folds, bookmarks.GetBookmarks(), (SubPage)subid);
//wxLogDebug(wxT(" %d (%d,%d,%d) pos:%d topline:%d"), i, di.type, di.document_id, di.version_id, pos, topline);
}
EditorCtrl* EditorCtrl::GetActiveEditor() { return this; }
const char** EditorCtrl::RecommendedIcon() const { return document_xpm; }
void EditorCtrl::CommandModeEnded() {
ClearSearchRange();
m_commandHandler.Clear();
}
void EditorCtrl::ClearRemoteInfo() {
if (m_remotePath.empty()) return;
m_remotePath.clear();
m_remoteProfile = NULL;
if (m_path.IsOk()) // Clean up temp buffer file
wxRemoveFile(m_path.GetFullPath());
}
unsigned int EditorCtrl::GetLength() const { return m_lines.GetLength(); }
unsigned int EditorCtrl::GetPos() const { return m_lines.GetPos(); }
void EditorCtrl::SetDocumentAndScrollPosition(int pos, int topline) {
m_lines.SetPos(pos);
scrollPos = m_lines.GetYPosFromLine(topline);
}
unsigned int EditorCtrl::GetCurrentLineNumber() {
// Rows count from 1; internally they count from 0.
return m_lines.GetCurrentLine() +1 ;
}
// Gets the current tab-expanded cursor position, counting from 1.
unsigned int EditorCtrl::GetCurrentColumnNumber() {
// Start of current line
const unsigned int line = m_lines.GetCurrentLine();
const unsigned int linestart = m_lines.GetLineStartpos(line);
// Cursor position within line
const unsigned int pos = m_lines.GetPos();
// Calculate pos with tabs expanded.
const unsigned int tab_size = this->m_tabWidth;
unsigned int tabpos = 0;
cxLOCKDOC_READ(m_doc)
for (doc_byte_iter dbi(doc, linestart); (unsigned int)dbi.GetIndex() < pos; ++dbi) {
// Only count first byte of UTF-8 multibyte chars
if ((*dbi & 0xC0) == 0x80) continue;
if (*dbi == '\t') {
// Figure out how far to advance to next tab stop
const int over = tabpos % tab_size;
tabpos += (tab_size - over);
}
else tabpos++;
}
cxENDLOCK;
// Columns are 1-indexed.
return tabpos + 1;
}
int EditorCtrl::GetTopLine() {
wxASSERT(scrollPos >= 0);
//wxLogDebug(wxT("GetTopLine() - %d %d - %s"), scrollPos, m_lines.GetLineFromYPos(scrollPos), GetPath());
return m_lines.GetLineFromYPos(scrollPos);
}
void EditorCtrl::Freeze() {
cxLOCKDOC_WRITE(m_doc)
doc.Freeze();
cxENDLOCK
}
doc_id EditorCtrl::GetDocID() const {
cxLOCKDOC_READ(m_doc)
return doc.GetDocument();
cxENDLOCK
}
doc_id EditorCtrl::GetLastStableDocID() const {
cxLOCKDOC_READ(m_doc)
doc_id di = doc.GetDocument();
if (doc.IsFrozen()) return di;
else return doc.GetDraftParent(di.version_id);
cxENDLOCK
}
wxString EditorCtrl::GetName() const {
cxLOCKDOC_READ(m_doc)
return doc.GetPropertyName();
cxENDLOCK
}
wxFontEncoding EditorCtrl::GetEncoding() const {
cxLOCKDOC_READ(m_doc)
return doc.GetPropertyEncoding();
cxENDLOCK
}
void EditorCtrl::SetEncoding(wxFontEncoding enc) {
cxLOCKDOC_WRITE(m_doc)
doc.Freeze();
doc.SetPropertyEncoding(enc);
doc.Freeze();
cxENDLOCK
MarkAsModified();
}
bool EditorCtrl::GetBOM() const {
cxLOCKDOC_READ(m_doc)
return doc.GetPropertyBOM();
cxENDLOCK
}
void EditorCtrl::SetBOM(bool saveBOM) {
cxLOCKDOC_WRITE(m_doc)
doc.Freeze();
doc.SetPropertyBOM(saveBOM);
doc.Freeze();
cxENDLOCK
MarkAsModified();
}
wxTextFileType EditorCtrl::GetEOL() const {
cxLOCKDOC_READ(m_doc)
return doc.GetPropertyEOL();
cxENDLOCK
}
void EditorCtrl::SetEOL(wxTextFileType eol) {
cxLOCKDOC_WRITE(m_doc)
doc.Freeze();
doc.SetPropertyEOL(eol);
doc.Freeze();
cxENDLOCK
MarkAsModified();
}
/*
void EditorCtrl::ActivateHighlight() {
m_usersStyler.Enable();
//DrawLayout();
}
void EditorCtrl::DisableHighlight() {
m_usersStyler.Disable();
//DrawLayout();
}
*/
void EditorCtrl::SetSearchHighlight(const wxString& pattern, int options) {
m_search_hl_styler.SetSearch(pattern, options);
}
void EditorCtrl::ClearSearchHighlight() {
m_search_hl_styler.Clear();
DrawLayout();
}
bool EditorCtrl::IsOk() const {
wxSize size = GetClientSize();
// It is ok to have a size 0 window with invalid bitmap
return (size.x == 0 && size.y == 0) || bitmap.Ok();
}
bool EditorCtrl::InChange() const {
cxLOCKDOC_READ(m_doc)
return doc.InChange();
cxENDLOCK
}
size_t EditorCtrl::GetChangeLevel() const {
cxLOCKDOC_READ(m_doc)
return doc.GetChangeLevel();
cxENDLOCK
}
void EditorCtrl::StartChange() {
cxLOCKDOC_WRITE(m_doc)
doc.StartChange(true/*doNotify*/);
cxENDLOCK
}
void EditorCtrl::EndChange(int forceTo) {
cxLOCKDOC_WRITE(m_doc)
doc.EndChange(forceTo);
cxENDLOCK
}
bool EditorCtrl::Show(bool show) {
bool result = wxControl::Show(show);
// All EditorCtrl's share the same bitmap.
// So we have to redraw it when the control is first shown
if (show) {
mdc.SelectObject(bitmap);
DrawLayout();
}
else mdc.SelectObject(wxNullBitmap);
return result;
}
void EditorCtrl::SetShowGutter(bool showGutter) {
if (m_showGutter == showGutter) return;
if (showGutter) m_gutterCtrl->Show();
else {
m_gutterCtrl->Hide();
m_gutterWidth = 0;
}
m_showGutter = showGutter;
DrawLayout();
}
void EditorCtrl::SetShowIndent(bool showIndent) {
m_lines.ShowIndent(showIndent);
DrawLayout();
}
void EditorCtrl::SetWordWrap(cxWrapMode wrapMode) {
if (m_lines.GetWrapMode() == wrapMode) return;
// Make sure we keep the same topline
topline = m_lines.GetLineFromYPos(scrollPos);
m_lines.SetWordWrap(wrapMode);
if (m_wrapAtMargin && wrapMode != cxWRAP_NONE)
m_lines.SetWidth(m_lines.GetMarginPos(), topline);
scrollPos = m_lines.GetYPosFromLine(topline);
DrawLayout();
}
void EditorCtrl::SetTabWidth(unsigned int width, bool soft_tabs, bool force, bool activeEditor) {
//If they set the tab size in the status bar, it changes it for all editors by default
//But, if they have applied a syntax specific tab size, and this is not the open editor, then ignore it.
if(!activeEditor && m_tabSettingsFromSyntax) return;
//If this is the open editor and the tab settings are set from the status bar, then ignore any settings from the syntax
if(force && activeEditor) {
m_tabSettingsOverriden = true;
m_tabSettingsFromSyntax = false;
}
// m_indent is the string used for indentation, either a real tab character
// or an appropriate number of spaces
if (soft_tabs)
m_indent = wxString(wxT(' '), width);
else
m_indent = wxString(wxT("\t"));
m_tabWidth = width;
m_softTabs = soft_tabs;
m_lines.SetTabWidth(width);
FoldingReIndent();
MarkAsModified();
}
void EditorCtrl::SetTabWidthFromSyntax() {
//If the user sets the tab settings from the status bar, then we are not going to override that with the syntax settings
if(m_tabSettingsOverriden) return;
unsigned int width;
bool softTabs;
eSettings& settings = eGetSettings();
wxString syntaxName = GetSyntaxName();
if(settings.GetTabWidth(syntaxName, width)) {
m_tabSettingsFromSyntax = true;
} else {
width = m_tabWidth;
}
if(settings.IsSoftTabs(syntaxName, softTabs)) {
m_tabSettingsFromSyntax = true;
} else {
softTabs = m_softTabs;
}
if(m_tabSettingsFromSyntax) {
SetTabWidth(width, softTabs, false);
}
}
const wxFont& EditorCtrl::GetEditorFont() const { return mdc.GetFont(); }
void EditorCtrl::SetGutterRight(bool doMove) {
m_gutterLeft = !doMove;
m_gutterCtrl->SetGutterRight(doMove);
DrawLayout();
}
bool EditorCtrl::HasScrollbar() const {
if (m_leftScrollbar) return m_leftScrollbar->IsShown();
else return (GetScrollThumb(wxVERTICAL) > 0);
}
void EditorCtrl::SetScrollbarLeft(bool doMove) {
if (doMove) {
m_leftScrollbar = new wxScrollBar(this, ID_LEFTSCROLL, wxPoint(0,0), wxDefaultSize, wxSB_VERTICAL);
m_leftScrollbar->SetCursor(*wxSTANDARD_CURSOR); // Set to standard cursor (otherwise it will inherit from editorCtrl)
const bool isShown = (GetScrollThumb(wxVERTICAL) > 0);
if (isShown) {
m_leftScrollWidth = m_leftScrollbar->GetSize().x;
SetScrollbar(wxVERTICAL, 0, 0, 0); // hide windows own scrollbar
return; // removing scrollbar will have sent draw event
}
}
else {
delete m_leftScrollbar;
m_leftScrollbar = NULL;
m_leftScrollWidth = 0;
}
m_isResizing = true;
DrawLayout();
}
//
// Returns true if a scrollbar was added or removed, else false.
//
bool EditorCtrl::UpdateScrollbars(unsigned int x, unsigned int y) {
const unsigned int height = m_lines.GetHeight();
// Check if we need a vertical scrollbar
if (m_leftScrollbar) {
const int scroll_thumb = m_leftScrollbar->GetThumbSize();
if (height > y) {
m_leftScrollbar->Show();
const int scroll_range = m_leftScrollbar->GetRange();
if (scroll_thumb != (int)y || scroll_range != (int)height) {
m_leftScrollbar->SetScrollbar(scrollPos, y, height, y);
if (scroll_thumb == 0) {
ReDraw();
return true; // cancel old redraw
}
}
// Avoid empty space at bottom
if (scrollPos + y > height) scrollPos = wxMax(0, height - y);
m_leftScrollbar->SetSize(-1, GetSize().y);
m_leftScrollbar->SetThumbPosition(scrollPos);
const unsigned int width = m_leftScrollbar->GetSize().x;
if (m_leftScrollWidth != width) {
m_leftScrollWidth = width;
ReDraw();
return true; // cancel old redraw
}
}
else if (scroll_thumb > 0) {
m_leftScrollbar->Hide();
m_leftScrollWidth = 0;
ReDraw();
return true; // cancel old redraw
}
}
else {
const int scroll_thumb = GetScrollThumb(wxVERTICAL);
if (height > y) {
const int scroll_range = GetScrollRange(wxVERTICAL);
if (scroll_thumb != (int)y || scroll_range != (int)height) {
SetScrollbar(wxVERTICAL, scrollPos, y, height);
if (scroll_thumb == 0) return true; // Creation of scrollbar have sent a size event
}
// Avoid empty space at bottom
if (scrollPos + y > height) scrollPos = wxMax(0, height - y);
SetScrollPos(wxVERTICAL, scrollPos);
}
else if (scroll_thumb > 0) {
SetScrollbar(wxVERTICAL, 0, 0, 0);
return true; // Removal of scrollbar have sent a size event
}
}
// Check if we need a horizontal scrollbar
const int scrollThumbX = GetScrollThumb(wxHORIZONTAL);
const unsigned int width = m_lines.GetWidth() + m_caretWidth;
if ((m_lines.GetWrapMode() == cxWRAP_NONE || m_wrapAtMargin) && width > x) {
m_scrollPosX = wxMin(m_scrollPosX, (int)(width-x));
m_scrollPosX = wxMax(0, m_scrollPosX);
SetScrollbar(wxHORIZONTAL, m_scrollPosX, x, width);
}
else if (scrollThumbX > 0) {
m_scrollPosX = 0;
SetScrollbar(wxHORIZONTAL, 0, 0, 0);
return true; // Removal of scrollbar have sent a size event
}
return false; // no scrollbar was added or removed
}
void EditorCtrl::DrawLayout(bool isScrolling) {
wxClientDC dc(this);
DrawLayout(dc, isScrolling);
}
void EditorCtrl::DrawLayout(wxDC& dc, bool WXUNUSED(isScrolling)) {
if (!IsShown() || !m_enableDrawing) return; // No need to draw
wxLogDebug(wxT("DrawLayout() : %d (%d,%d)"), GetId(), m_enableDrawing, IsShown());
//wxLogDebug(wxT("DrawLayout() : %s"), GetName());
if (m_beforeRedrawCallback) m_beforeRedrawCallback(m_callbackData);
wxASSERT(m_scrollPosX >= 0);
// Check if we should cancel a snippet
if (m_snippetHandler.IsActive()) m_snippetHandler.Validate();
// Get view dimensions
const wxSize size = GetClientSize();
if (size.x == 0 || size.y == 0) return; // Nothing to draw
if (m_showGutter) {
m_gutterWidth = m_gutterCtrl->CalcLayout(size.y);
// Move gutter to correct position
const unsigned int gutterxpos = m_gutterLeft ? m_leftScrollWidth : size.x - m_gutterWidth;
if (m_gutterCtrl->GetPosition().x != (int)gutterxpos) {
m_gutterCtrl->SetPosition(wxPoint(gutterxpos, 0));
}
}
const unsigned int editorSizeX = ClientWidthToEditor(size.x);
// There may only be room for the gutter
if (editorSizeX == 0) {
if (m_showGutter) m_gutterCtrl->DrawGutter();
return;
}
// Resize the bitmap used for doublebuffering
if (bitmap.GetWidth() < (int)editorSizeX || bitmap.GetHeight() < size.y) {
// disassociate and release mem for old bitmap
mdc.SelectObjectAsSource(wxNullBitmap);
bitmap = wxNullBitmap;
// Resize bitmap
bitmap = wxBitmap(size.x, size.y);
mdc.SelectObject(bitmap);
}
// We always have to reselect the bitmap if it has been resized
// by another editorCtrl. Otherwise the memorydc will get confused.
if (mdc.GetSize() != wxSize(bitmap.GetWidth(), bitmap.GetHeight())) {
mdc.SelectObject(bitmap);
}
// Verify scrollPos (might come from an un-updated scrollbar)
if (scrollPos < 0) {
// Scroll value of -1 indicates that we should move to the
// bottom of the document.
wxASSERT(scrollPos == -1); // only valid neg. value
//wxASSERT(size.x == m_lines.GetWidth()); // should never come with a size change
m_lines.PrepareAll(); // make sure all line positions are valid
scrollPos = wxMax(0, m_lines.GetHeight()-size.y);
}
else {
scrollPos = wxMin(scrollPos, wxMax(0, m_lines.GetHeight()-size.y));
if (m_isResizing && editorSizeX != m_lines.GetDisplayWidth()) {
const int newtopline = m_lines.GetLineFromYPos(scrollPos);
const unsigned int lineWidth = (m_wrapAtMargin && m_lines.GetWrapMode() != cxWRAP_NONE) ? m_lines.GetMarginPos() : editorSizeX;
m_lines.SetWidth(lineWidth, newtopline);
// If there are folds, we have to make sure all line positions are valid
if (HasFoldedFolds()) m_lines.PrepareAll();
// Lock topline on resize
if (topline == -1) topline = newtopline;
scrollPos = m_lines.GetYPosFromLine(newtopline);
}
else {
topline = -1;
scrollPos += m_lines.PrepareYPos(scrollPos);
scrollPos = wxMin(scrollPos, m_lines.GetHeight()-size.y);
scrollPos = wxMax(0, scrollPos);
}
}
wxASSERT(scrollPos >= 0);
wxASSERT(scrollPos <= m_lines.GetHeight());
// Check if we need to adjust scrollbar
if (UpdateScrollbars(editorSizeX, size.y)) return; // adding/removing scrollbars send size event
// Make sure we remove un-needed stylers
if (!(m_parentFrame.IsSearching() || m_commandHandler.IsSearching()))
m_search_hl_styler.Clear(); // Only highlight search terms during search
// When scrolling, we can just draw the new parts
wxRect rect(0, scrollPos, editorSizeX, size.y);
/*if (isScrolling && scrollPos != old_scrollPos && this == FindFocus()) {
// If there is overlap, then move the image
if (scrollPos + size.y > old_scrollPos && scrollPos < old_scrollPos + size.y) {
const int top = wxMax(scrollPos, old_scrollPos);
const int bottom = wxMin(scrollPos, old_scrollPos) + size.y;
const int overlap_height = bottom - top;
#ifdef __WXMSW__
::BitBlt(GetHdcOf(mdc), 0, (top - old_scrollPos) + (old_scrollPos - scrollPos), editorSizeX, overlap_height, GetHdcOf(mdc), 0, top - old_scrollPos, SRCCOPY);
#else
mdc.Blit(0, (top - old_scrollPos) + (old_scrollPos - scrollPos), editorSizeX, overlap_height, &mdc, 0, top - old_scrollPos);
#endif
// Calculate rect of newly revealed part
const int new_height = size.y - overlap_height;
const int newtop = (top == scrollPos ? bottom : scrollPos);
rect = wxRect(0,newtop,editorSizeX,new_height);
wxASSERT(newtop <= m_lines.GetHeight());
}
}*/
// Highlight matching brackets
MatchBrackets();
// Set the theme colors
mdc.SetTextForeground(m_theme.foregroundColor);
mdc.SetBrush(wxBrush(m_theme.backgroundColor));
mdc.SetPen(wxPen(m_theme.backgroundColor));
// Clear the background
mdc.DrawRectangle(rect.x, rect.y - scrollPos, rect.width, rect.height);
// Draw the layout to MemoryDC
m_lines.Draw(-m_scrollPosX, -scrollPos, rect);
// During the draw we may have corrected some approximated
// line dimensions causing the dimesions of the entire document
// to have changed. So we have to check if the scrollbars should
// be updated again.
if (UpdateScrollbars(editorSizeX, size.y)) return; // adding/removing scrollbars send size event
// avoid leaving a caret trace
if (caret->IsVisible()) caret->Hide();
// Copy MemoryDC to Display
const unsigned int xpos = m_leftScrollWidth + (m_gutterLeft ? m_gutterWidth : 0);
#ifdef __WXMSW__
::BitBlt(GetHdcOf(dc), xpos, 0,(int)editorSizeX, (int)size.y, GetHdcOf(mdc), 0, 0, SRCCOPY);
#else
dc.Blit(xpos, 0, editorSizeX, size.y, &mdc, 0, 0);
#endif
if (m_lines.IsCaretInPreparedPos()) {
// Move the caret to the new position
m_lines.UpdateCaretPos();
if (IsCaretVisible()) {
const wxPoint cpos = GetCaretPoint();
caret->Move(cpos);
caret->Show();
}
}
// Draw the gutter
if (m_showGutter) m_gutterCtrl->DrawGutter();
// Check if we should send notification about scrolling
if (m_isResizing || scrollPos != old_scrollPos) {
if (m_scrollCallback) m_scrollCallback(m_callbackData);
old_scrollPos = scrollPos;
}
if (m_afterRedrawCallback) m_afterRedrawCallback(m_callbackData);
m_isResizing = false;
}
unsigned int EditorCtrl::ClientWidthToEditor(unsigned int width) const {
return (width > m_gutterWidth) ? (width - (m_gutterWidth + m_leftScrollWidth)) : 0;
}
wxPoint EditorCtrl::ClientPosToEditor(unsigned int xpos, unsigned int ypos) const {
unsigned int adjXpos = (xpos - m_leftScrollWidth) + m_scrollPosX;
if (m_gutterLeft) adjXpos -= m_gutterWidth;
return wxPoint(adjXpos, ypos + scrollPos);
}
wxPoint EditorCtrl::EditorPosToClient(unsigned int xpos, unsigned int ypos) const {
unsigned int adjXpos = (xpos - m_scrollPosX) + m_leftScrollWidth;
if (m_gutterLeft) adjXpos += m_gutterWidth;
return wxPoint(adjXpos, ypos - scrollPos);
}
wxPoint EditorCtrl::GetCaretPoint() const {
const wxPoint cpos = m_lines.GetCaretPos();
return EditorPosToClient(cpos.x, cpos.y);
}
unsigned int EditorCtrl::UpdateEditorWidth() {
const wxSize size = GetClientSize();
const unsigned int width = ClientWidthToEditor(size.x);
m_lines.SetWidth(width);
return width;
}
wxString EditorCtrl::GetNewIndentAfterNewline(unsigned int lineid) {
wxASSERT(lineid < m_lines.GetLineCount());
int i = lineid;
for (; i >= 0; --i) {
const unsigned int linestart = m_lines.GetLineStartpos(i);
const unsigned int lineend = m_lines.GetLineEndpos(i, true);
if (linestart < lineend) {
const deque<const wxString*> scope = m_syntaxstyler.GetScope(linestart);
// Check if we should ignore the indentation on current line
const wxString& ignorePattern = m_syntaxHandler.GetIndentNonePattern(scope);
if (!ignorePattern.empty()) {
const search_result res = RegExFind(ignorePattern, linestart, false, NULL, lineend);
if (res.error_code > 0)
continue;
}
// Check if indentation should increase
const wxString& increasePattern = m_syntaxHandler.GetIndentIncreasePattern(scope);
if (!increasePattern.empty()) {
const search_result res = RegExFind(increasePattern, linestart, false, NULL, lineend);
if (res.error_code > 0)
return m_lines.GetLineIndent(i) + m_indent;
}
/*// Check if only next line should be indented
const wxString& nextPattern = m_syntaxHandler.GetIndentNextPattern(scope);
if (!nextPattern.empty()) {
const search_result res = RegExFind(nextPattern, linestart, false, NULL, lineend);
if (res.error_code > 0) {
//if (i) {
// // If the line above is already nextLineIndented, we should keep same indent
// // (we assume same pattern is valid)
// const unsigned int linestart2 = m_lines.GetLineStartpos(i-1);
// const unsigned int lineend2 = m_lines.GetLineEndpos(i-1, true);
// if (linestart2 < lineend2) {
// const search_result res2 = RegExFind(nextPattern, linestart2, false, NULL, lineend2);
// if (res.error_code > 0) return m_lines.GetLineIndent(i+1);
// }
//}
return GetRealIndent(i+1); //+ m_indent;
}
}*/
}
// If we get to here we should just use the same indent as the line above
break;
}
return m_lines.GetLineIndent( (i == -1) ? 0 : i );
}
wxString EditorCtrl::GetRealIndent(unsigned int lineid, bool newline, bool skipWhitespaceOnlyLines) {
if (lineid == 0) return m_lines.GetLineIndent(0);
wxASSERT(lineid < m_lines.GetLineCount());
unsigned int linestart = m_lines.GetLineStartpos(lineid);
unsigned int lineend;
// The real indentation level of a line is defined by the lines above it
unsigned int validLine = lineid;
wxString indent;
// We assume all the lines closely above have same indent rules
const deque<const wxString*> scope = m_syntaxstyler.GetScope(linestart);
const wxString& unIndentedLinePattern = m_syntaxHandler.GetIndentNonePattern(scope);
const wxString& indentNextLinePattern = m_syntaxHandler.GetIndentNextPattern(scope);
const wxString& increasePattern = m_syntaxHandler.GetIndentIncreasePattern(scope);
int i = lineid-1;
for (;;) {
// Move up through the lines until we find one with valid indentation
if (!unIndentedLinePattern.empty()) {
for (; i >= 0; --i) {
linestart = m_lines.GetLineStartpos(i);
lineend = m_lines.GetLineEndpos(i, true);
// Ignore unindented lines
const search_result res = RegExFind(unIndentedLinePattern, linestart, false, NULL, lineend);
if (res.error_code <= 0) {
if(skipWhitespaceOnlyLines) {
cxLOCKDOC_READ(m_doc)
while(linestart < lineend) {
const wxChar c = doc.GetChar(linestart);
if(!(c == ' ' || c == '\t')) break;
linestart++;
}
cxENDLOCK
if(linestart == lineend) continue;
}
validLine = i;
break;
}
}
}
if (validLine == lineid) break;
linestart = m_lines.GetLineStartpos(validLine);
lineend = m_lines.GetLineEndpos(validLine, true);
// Increasing line is ok (but we have to use it's indentation)
if (!increasePattern.empty()) {
const search_result res = RegExFind(increasePattern, linestart, false, NULL, lineend);
if (res.error_code > 0) {
indent = m_indent; // increase with one
break;
}
}
// Nextline indenter is ok (but don't indent if target is nextlineindent as well)
if (!indentNextLinePattern.empty()) {
const search_result res = RegExFind(indentNextLinePattern, linestart, false, NULL, lineend);
if (res.error_code > 0) {
// Multiple nextLineIndented lines in sequence should have same indent
const unsigned int linestart2 = m_lines.GetLineStartpos(lineid);
const unsigned int lineend2 = m_lines.GetLineEndpos(lineid, true);
if (linestart2 != lineend2) { // never increase indent on new line
const search_result res2 = RegExFind(indentNextLinePattern, linestart2, false, NULL, lineend2);
if (res2.error_code <= 0) indent = m_indent; // increase with one
}
break;
}
// If valid line does not have any specific indent rule
// we have to check whether it is nextlineindented by line above
for (--i; i >= 0; --i) {
linestart = m_lines.GetLineStartpos(i);
lineend = m_lines.GetLineEndpos(i, true);
// Ignore unindented lines
if (!unIndentedLinePattern.empty()) {
const search_result res = RegExFind(unIndentedLinePattern, linestart, false, NULL, lineend);
if (res.error_code > 0) continue;
}
const search_result res = RegExFind(indentNextLinePattern, linestart, false, NULL, lineend);
if (res.error_code > 0) validLine = i;
break;
}
}
break;
}
indent += m_lines.GetLineIndent(validLine);
// Check if the current line should be de-dented
if (!newline && validLine < lineid) {
linestart = m_lines.GetLineStartpos(lineid);
lineend = m_lines.GetLineEndpos(lineid, true);
const wxString& decreasePattern = m_syntaxHandler.GetIndentDecreasePattern(scope);
if (!decreasePattern.empty()) {
const search_result res = RegExFind(decreasePattern, linestart, false, NULL, lineend);
if (res.error_code > 0) {
// Decrease tab level with one
if (!indent.empty()) {
const unsigned int tabWidth = m_tabWidth;
if (indent[0] == wxT('\t')) indent.Remove(0, 1);
else if (indent.size() >= tabWidth) indent.Remove(0, tabWidth);
}
return indent;
}
}
}
// Return indent of line
return indent;
}
// Used by SnippetHandler
wxString EditorCtrl::GetLineIndentFromPos(unsigned int pos) {
wxASSERT(pos <= GetLength());
const unsigned int lineid = m_lines.GetLineFromCharPos(pos);
return m_lines.GetLineIndent(lineid);
}
bool EditorCtrl::IsSpaces(unsigned int start, unsigned int end) const {
wxASSERT(start <= end && end <= m_lines.GetLength());
if (start == end) return false;
cxLOCKDOC_READ(m_doc)
doc_byte_iter dbi(doc, start);
while ((unsigned int)dbi.GetIndex() < end) {
if (*dbi != ' ') return false;
++dbi;
}
return true;
cxENDLOCK
}
unsigned int EditorCtrl::CountMatchingChars(wxChar match, unsigned int start, unsigned int end) const {
wxASSERT(start <= end && end <= m_lines.GetLength());
if (start == end) return 0;
const wxString text = GetText(start, end);
unsigned int count = 0;
for (unsigned int i = 0; i < text.size(); ++i)
if (text[i] == match) ++count;
return count;
}
wxString EditorCtrl::GetText() const {
return GetText(0, GetLength());
}
void EditorCtrl::WriteText(wxOutputStream& stream) const {
cxLOCKDOC_READ(m_doc)
doc.WriteText(stream);
cxENDLOCK
}
wxString EditorCtrl::GetText(unsigned int start, unsigned int end) const {
wxASSERT(start <= end && end <= m_lines.GetLength());
cxLOCKDOC_READ(m_doc)
return doc.GetTextPart(start, end);
cxENDLOCK
}
void EditorCtrl::GetText(vector<char>& text) const {
GetTextPart(0, GetLength(), text);
}
void EditorCtrl::GetTextPart(unsigned int start, unsigned int end, vector<char>& text) const {
if (start == end) return;
const unsigned int len = end - start;
text.resize(len);
cxLOCKDOC_READ(m_doc)
doc.GetTextPart(start, end, (unsigned char*)&*text.begin());
cxENDLOCK
}
int GetTabWidthInSpaces(wxString text, int tabWidth) {
int length = 0;
for(unsigned int c = 0; c < text.length(); c++) {
if(text[c] == ' ') {
length++;
} else if(text[c] == '\t') {
//If a line just has \t...., then the tab will expand to 4 spaces, for a total of 8.
//If a line has ..\t.... though, it might only 'take up' 2 spaces, for a total of 8.
length += (tabWidth - (length%tabWidth));
}
}
return length;
}
bool EditorCtrl::SmartTab() {
bool smartTabsEnabled = false;
eGetSettings().GetSettingBool(wxT("smartTabs"), smartTabsEnabled);
if(!smartTabsEnabled) return false;
const unsigned int linestart = m_lines.GetLineStartpos(m_lines.GetCurrentLine());
const unsigned int lineend = m_lines.GetLineEndpos(m_lines.GetCurrentLine());
const unsigned int tabWidth = m_tabWidth;
unsigned int whitespaceEnd = linestart;
unsigned int pos = GetPos();
cxLOCKDOC_READ(m_doc)
while(whitespaceEnd < lineend) {
const wxChar c = doc.GetChar(whitespaceEnd);
if(!(c == ' ' || c == '\t')) break;
whitespaceEnd++;
}
cxENDLOCK
if(whitespaceEnd >= lineend) {
//the line is all whitespace
wxString realIndent = GetRealIndent(m_lines.GetCurrentLine(), false, true);
int realWidth = GetTabWidthInSpaces(realIndent, tabWidth);
int currentWidth = 0;
cxLOCKDOC_READ(m_doc)
currentWidth = GetTabWidthInSpaces(doc.GetTextPart(linestart, lineend), tabWidth);
cxENDLOCK
//If we are not at or past the previous line's indentation level, then let's jump to it
if(currentWidth < realWidth) {
int difference = realWidth - currentWidth;
if (!m_softTabs) { // Hard Tab
//if there are 7 spaces on the line, but the real indent is 12, that is a difference of 5. Say the tabWidth is 4.
//We need to insert two tabs then. The second line takes care of that by increasing the difference from 5 to 8 in this example.
difference += difference % tabWidth;
for(int numTabs = difference / tabWidth; numTabs > 0; numTabs--) {
InsertChar(wxChar('\t'));
}
} else {
const wxString indent(wxT(' '), difference);
Insert(indent);
}
SetPos(m_lines.GetLineEndpos(m_lines.GetCurrentLine()));
return true;
} else if (GetPos() < lineend && !m_lines.IsSelected()) {
//We are somewhere in the middle of a line of indentation, but we already have enough indents to match the previous line.
//First just jump to the end of the line. Subsequent tabs will insert tabs, but this will not.
SetPos(m_lines.GetLineEndpos(m_lines.GetCurrentLine()));
return true;
} else {
// Insert a new tab (done below)
}
} else if(pos < whitespaceEnd) {
//the cursor is in the leading whitespace of the line, so we will move the cursor to the end of the whitespace
SetPos(whitespaceEnd);
return true;
}
return false;
}
void EditorCtrl::GetLine(unsigned int lineid, vector<char>& text) const {
unsigned int start;
unsigned int end;
m_lines.GetLineExtent(lineid, start, end);
GetTextPart(start, end, text);
}
void EditorCtrl::Tab() {
const bool shiftDown = wxGetKeyState(WXK_SHIFT);
const bool isMultiSel = m_lines.IsSelectionMultiline() && !m_lines.IsSelectionShadow();
// If there are multiple lines selected then tab triggers indentation
if (m_snippetHandler.IsActive()) {
if (shiftDown) {
if (m_macro.IsRecording()) m_macro.Add(wxT("PrevSnippetField"));
m_snippetHandler.PrevTab();
}
else {
if (m_macro.IsRecording()) m_macro.Add(wxT("NextSnippetField"));
m_snippetHandler.NextTab();
}
return;
}
if (shiftDown || isMultiSel) {
if (lastaction != ACTION_NONE || lastpos != GetPos()) Freeze();
const bool addIndent = !shiftDown;
if (m_macro.IsRecording()) {
if (addIndent) m_macro.Add(wxT("IndentSelectedLines"));
else m_macro.Add(wxT("DedentSelectedLines"));
}
IndentSelectedLines(addIndent);
lastpos = GetPos();
lastaction = ACTION_NONE;
return;
}
unsigned int pos = GetPos();
if (m_macro.IsRecording()) m_macro.Add(wxT("Tab"));
// If the tab is preceded by a word it can trigger a snippet
if (DoTabTrigger(pos)) return;
if(SmartTab()) return;
// If we get to here we have to insert a real tab
if (!m_softTabs) { // Hard Tab
InsertChar(wxChar('\t'));
return;
}
// Soft Tab (incremental number of spaces)
const unsigned int linestart = m_lines.GetLineStartpos(m_lines.GetCurrentLine());
const unsigned int tabWidth = m_tabWidth;
// Calculate pos with tabs expanded
unsigned int tabpos = 0;
cxLOCKDOC_READ(m_doc)
for (doc_byte_iter dbi(doc, linestart); (unsigned int)dbi.GetIndex() < pos; ++dbi) {
// Only count first byte of UTF-8 multibyte chars
if ((*dbi & 0xC0) == 0x80) continue;
if (*dbi == '\t') tabpos += tabWidth;
else tabpos += 1;
}
cxENDLOCK;
// Insert as many spaces as it takes to get to next tabstop
const unsigned int spaces = tabpos % tabWidth;
const wxString indent(wxT(' '), (spaces == 0 || spaces == tabWidth) ? tabWidth : tabWidth - spaces);
Insert(indent);
}
bool EditorCtrl::DoTabTrigger(unsigned int pos) {
wxASSERT(pos <= m_lines.GetLength());
wxString strPart; // part of the current line to check trigger actions
cxLOCKDOC_READ(m_doc)
strPart = doc.GetTextPart(doc.GetLineStart(pos), pos);
cxENDLOCK
if (strPart.empty()) return false; // empty string is not trigger
const deque<const wxString*> scope = m_syntaxstyler.GetScope(pos);
const vector<const tmAction*> actions = m_syntaxHandler.GetActions(strPart, scope);
if (actions.empty()) return false; // no action found for trigger
// Present user with a list of actions
int actionIndex = 0;
if (actions.size() > 1) {
actionIndex = ShowPopupList(actions);
if (actionIndex == -1)
return true;
}
// Clean up first
if (!m_lines.IsSelectionShadow()) RemoveAllSelections();
m_currentSel = -1;
m_snippetHandler.Clear(); // stop any active snippets
// Remove the trigger
Freeze();
RawDelete(pos - actions[actionIndex]->trigger.Len(), pos);
// Do the Action
DoAction(*actions[actionIndex], NULL, true);
return true;
}
void EditorCtrl::FilterThroughCommand() {
RunCmdDlg dlg(this, eGetSettings());
if (dlg.ShowModal() == wxID_OK) {
const tmCommand cmd = dlg.GetCommand();
if (m_macro.IsRecording()) {
eMacroCmd& mc = m_macro.Add(wxT("FilterThroughCommand"));
mc.AddArg(wxT("command"), cmd.name);
switch (cmd.input) {
case tmCommand::ciNONE: mc.AddArg(wxT("input"), wxT("none")); break;
case tmCommand::ciSEL: mc.AddArg(wxT("input"), wxT("selection")); break;
case tmCommand::ciDOC: mc.AddArg(wxT("input"), wxT("document")); break;
};
switch (cmd.output) {
case tmCommand::coNONE: mc.AddArg(wxT("output"), wxT("discard")); break;
case tmCommand::coSEL: mc.AddArg(wxT("output"), wxT("replaceSelectedText")); break;
case tmCommand::coDOC: mc.AddArg(wxT("output"), wxT("replaceDocument")); break;
case tmCommand::coINSERT: mc.AddArg(wxT("output"), wxT("afterSelectedText")); break;
case tmCommand::coSNIPPET: mc.AddArg(wxT("output"), wxT("insertAsSnippet")); break;
case tmCommand::coHTML: mc.AddArg(wxT("output"), wxT("showAsHTML")); break;
case tmCommand::coTOOLTIP: mc.AddArg(wxT("output"), wxT("showAsTooltip")); break;
case tmCommand::coNEWDOC: mc.AddArg(wxT("output"), wxT("openAsNewDocument")); break;
}
}
DoAction(cmd, NULL, false);
}
}
void EditorCtrl::DoActionFromDlg() {
const deque<const wxString*> scope = m_syntaxstyler.GetScope(GetPos());
vector<const tmAction*> actions;
m_syntaxHandler.GetAllActions(scope, actions);
FindCmdDlg dlg(this, actions);
if (dlg.ShowModal() == wxID_OK) {
const tmAction* action = dlg.GetSelection();
if (action) DoAction(*action, NULL, false);
}
}
void EditorCtrl::DoAction(const tmAction& action, const map<wxString, wxString>* envVars, bool isRaw) {
wxLogDebug(wxT("Doing action '%s' from bundle '%s'"), action.name.c_str(), action.bundle ? action.bundle->name.c_str() : wxT("none"));
if (m_macro.IsRecording()) {
eMacroCmd& cmd = m_macro.Add(wxT("RunBundleCommand"));
cmd.AddArg(wxT("uuid"), action.uuid);
cmd.AddArg(wxT("path"), m_syntaxHandler.GetBundleItemUriFromUuid(action.uuid)); // for info only
}
MacroDisabler m(m_macro); // Avoid dublicate recording
if (action.IsSyntax()) {
SetSyntax(action.name);
return;
}
if (action.IsMacro()) {
const eMacro macro = m_syntaxHandler.GetMacroContent(action);
PlayMacro(macro);
ReDraw();
return;
}
if (m_activeTooltip) m_activeTooltip->Close();
// Get Action contents
const vector<char>& cmdContent = m_syntaxHandler.GetActionContent(action);
if (cmdContent.empty()) return; // nothing to do
// Set up the process environment
cxEnv env;
SetEnv(env, action.isUnix, action.bundle);
if (envVars) env.SetEnv(*envVars);
if (!isRaw) Freeze();
// Set shell variables from bundle prefs
const deque<const wxString*> scope = m_syntaxstyler.GetScope(GetPos());
map<wxString, wxString> shellVars = m_syntaxHandler.GetShellVariables(scope);
env.SetEnv(shellVars);
// Set current working dir to document location
wxString cwd;
if (m_path.IsOk()) cwd = m_path.GetPath();
if (action.IsSnippet()) {
DeleteSelections();
m_snippetHandler.StartSnippet(cmdContent, &env, action.bundle);
}
else if (action.IsCommand()) {
#ifdef __WXMSW__
if (action.isUnix && !eDocumentPath::InitCygwin()) return;
#endif // __WXMSW__
const tmCommand* cmd = (tmCommand*)&action;
// beforeRunningCommand
if (cmd->save == tmCommand::csDOC && IsModified()) {
if (!SaveText()) return;
m_parentFrame.UpdateWindowTitle(); // update tabs
}
else if (cmd->save == tmCommand::csALL) {
if (m_parentFrame.HasProject()) {
// Ask parent to save all modified docs
m_parentFrame.SaveAllFilesInProject();
}
else {
if (!SaveText()) return;
m_parentFrame.UpdateWindowTitle(); // update tabs
}
}
// Establish input source
tmCommand::CmdInput src = cmd->input;
bool isSelectionInput = false;
if (src == tmCommand::ciSEL) {
if (!IsSelected()) {
//if (cmd->fallbackInput == tmCommand::ciNONE) {
// src = tmCommand::ciDOC;
//}
//else src = cmd->fallbackInput;
src = cmd->fallbackInput;
}
isSelectionInput = true;
}
// Get the input
unsigned int selStart = GetPos();
unsigned int selEnd = selStart;
switch (src) {
case tmCommand::ciSEL:
{
const interval* const sel = m_lines.FirstSelection();
if (sel) sel->Get(selStart, selEnd);
}
break;
case tmCommand::ciDOC:
selStart = 0;
selEnd = GetLength();
break;
case tmCommand::ciLINE:
{
const unsigned int cl = m_lines.GetCurrentLine();
selStart = m_lines.GetLineStartpos(cl);
selEnd = m_lines.GetLineEndpos(cl);
}
break;
case tmCommand::ciWORD:
{
const interval iv = GetWordIv(selStart);
selStart = iv.start;
selEnd = iv.end;
}
break;
case tmCommand::ciCHAR:
if (selStart == GetLength()) break;
cxLOCKDOC_READ(m_doc)
selEnd = doc.GetNextCharPos(selStart);
cxENDLOCK
break;
case tmCommand::ciNONE:
break;
case tmCommand::ciSCOPE:
const deque<interval> scopes = m_syntaxstyler.GetScopeIntervals(GetPos());
if (!scopes.empty()) {
const interval& iv = scopes.back();
selStart = iv.start;
selEnd = iv.end;
}
break;
}
// Get the input
vector<char> input;
if (selStart < selEnd) {
const unsigned int inputLen = selEnd - selStart;
if (cmd->inputXml) {
m_syntaxstyler.GetTextWithScopes(selStart, selEnd, input);
}
else {
input.resize(inputLen);
cxLOCKDOC_READ(m_doc)
doc.GetTextPart(selStart, selEnd, (unsigned char*)&*input.begin());
cxENDLOCK
}
}
// If there is input we have to set the TM_INPUT_START_* env vars
// which contains the input start relative to the line start
if (!input.empty() && src != tmCommand::ciDOC) {
const unsigned int lineNum = m_lines.GetLineFromCharPos(selStart);
const unsigned int lineStart = m_lines.GetLineStartpos(lineNum);
const unsigned int lineIndex = selStart - lineStart;
env.SetEnv(wxT("TM_INPUT_START_LINE"), wxString::Format(wxT("%u"), lineNum+1));
env.SetEnv(wxT("TM_INPUT_START_LINE_INDEX"), wxString::Format(wxT("%u"), lineIndex));
env.SetEnv(wxT("TM_INPUT_START_COLUMN"), wxString::Format(wxT("%u"), lineIndex+1));
}
// Run command
vector<char> output;
vector<char> errout;
int pid;
{
wxWindowDisabler wd;
pid = ShellRunner::RawShell(cmdContent, input, &output, &errout, env, action.isUnix, cwd);
}
if (pid != 0) wxLogDebug(wxT("shell returned pid = %d"), pid);
tmCommand::CmdOutput outputMode = cmd->output;
if (pid == 200) outputMode = tmCommand::coNONE;
else if (pid == 201) outputMode = tmCommand::coSEL;
else if (pid == 202) outputMode = tmCommand::coDOC;
else if (pid == 203) outputMode = tmCommand::coINSERT;
else if (pid == 204) outputMode = tmCommand::coSNIPPET;
else if (pid == 205) outputMode = tmCommand::coHTML;
else if (pid == 206) outputMode = tmCommand::coTOOLTIP;
else if (pid == 207) outputMode = tmCommand::coNEWDOC;
// Handle output
if (outputMode != tmCommand::coNONE) {
wxString shellout;
wxString shellerr;
wxString out;
if (!output.empty()) shellout = wxString(&*output.begin(), wxConvUTF8, output.size());
if (!errout.empty()) shellerr = wxString(&*errout.begin(), wxConvUTF8, errout.size());
out = shellout + shellerr;
#ifdef __WXMSW__
// WINDOWS ONLY!! newline conversion
out.Replace(wxT("\r\n"), wxT("\n"));
#endif // __WXMSW__
// End if we could not run command and there is no output
if (pid != -1 || !out.empty()) {
if (!isSelectionInput) {
const interval* const sel = m_lines.FirstSelection();
if (sel) sel->Get(selStart, selEnd);
else selStart = selEnd = GetPos();
}
RemoveAllSelections();
switch (outputMode) {
case tmCommand::coDOC:
selStart = 0;
selEnd = GetLength();
case tmCommand::coSEL:
{
unsigned int pos = GetPos();
RawDelete(selStart, selEnd);
const unsigned int bytelen = RawInsert(selStart, out);
if (src == tmCommand::ciSEL) {
const unsigned int endPos = selStart + bytelen;
m_lines.AddSelection(selStart, endPos);
SetPos(endPos);
}
else {
// Try to set a position as close a possible to previous
if (pos > GetLength()) SetPos(GetLength());
else {
cxLOCKDOC_READ(m_doc)
pos = doc.GetValidCharPos(pos);
cxENDLOCK
SetPos(pos);
}
}
}
break;
case tmCommand::coINSERT:
{
const unsigned int bytelen = RawInsert(selEnd, out);
SetPos(selEnd + bytelen);
}
break;
case tmCommand::coTOOLTIP:
out.Trim(); // strip trailing spaces & newlines
ShowTipAtCaret(out);
break;
case tmCommand::coSNIPPET:
{
RawDelete(selStart, selEnd);
SetPos(selStart);
if (!output.empty())
m_snippetHandler.StartSnippet(output, &env, action.bundle);
}
break;
case tmCommand::coHTML:
// Only show stderr in HTML window if there is no other output
m_parentFrame.ShowOutput(cmd->name, !shellout.empty() ? shellout : shellout);
break;
case tmCommand::coNEWDOC:
{
// Create new document
doc_id di;
cxLOCK_WRITE(m_catalyst)
Document newdoc(catalyst.NewDocument(), m_catalyst);
newdoc.Insert(0, out);
newdoc.Freeze();
di = newdoc.GetDocument();
cxENDLOCK
m_parentFrame.OpenDocument(di);
}
break;
case tmCommand::coREPLACEDOC:
{
RawDelete(0, GetLength());
RawInsert(0, out);
SetPos(0);
}
break;
case tmCommand::coNONE:
break;
}
}
}
// If the command has modified the file on disk, and we have not changed doc
// since last save, the we automatically reload
if (!IsEmpty() && !IsModified() && !IsRemote() && !IsBundleItem()) {
// Get mirror info
doc_id di;
wxDateTime mDate;
cxLOCK_READ(m_catalyst)
catalyst.GetFileMirror(m_path.GetFullPath(), di, mDate);
cxENDLOCK
// Check if file exists
if (mDate.IsValid() && m_path.FileExists()) {
wxDateTime modDate = m_path.GetModificationTime();
if (modDate.IsValid()) { // file could be locked by another app
// Windows does not really handle the minor parts of file dates
mDate.SetMillisecond(0);
modDate.SetMillisecond(0);
if (mDate != modDate)
LoadText(m_path.GetFullPath());
}
}
}
}
if (!isRaw) {
MakeCaretVisible();
DrawLayout();
// command may have shown a dialog
//Raise();
SetFocus();
}
}
unsigned int EditorCtrl::RawInsert(unsigned int pos, const wxString& text, bool doSmartType) {
if (text.empty()) return 0;
wxASSERT(pos <= m_lines.GetLength());
m_autopair.ClearIfInsertingOutsideInnerPair(pos);
wxString autoPair;
if (doSmartType) {
// Check if we are inserting at end of inner pair
if (m_autopair.HasPairs()) { // we must be at end
wxChar c;
cxLOCKDOC_READ(m_doc)
c = doc.GetChar(pos);
cxENDLOCK
// Jump over auto-paired char
if (text == c) {
cxLOCKDOC_READ(m_doc)
pos = doc.GetNextCharPos(pos);
cxENDLOCK
m_lines.SetPos(pos);
m_autopair.DropInnerPair();
return 0;
}
}
if (text.length() == 1)
autoPair = AutoPair(pos, text);
}
// Insert the text
unsigned int byte_len = 0;
cxLOCKDOC_WRITE(m_doc)
byte_len = doc.Insert(pos, text);
cxENDLOCK
m_lines.Insert(pos, byte_len); // Update the view
StylersInsert(pos, byte_len); // Update stylers
// Smart Typing Pairs
if (doSmartType) {
// Caret is moved to end of insertion (but before smart pair)
unsigned int pairpos = pos + byte_len;
m_lines.SetPos(pairpos);
if (autoPair.empty()) m_autopair.AdjustEndsUp(byte_len); // Adjust containing pairs
else {
// insert paired char
unsigned int pair_len = 0;
cxLOCKDOC_WRITE(m_doc)
pair_len = doc.Insert(pairpos, autoPair);
cxENDLOCK
m_lines.Insert(pairpos, pair_len); // Update the view
StylersInsert(pairpos, pair_len); // Update stylers
byte_len += pair_len;
}
}
else {
// Ensure carret stays at same position
unsigned int caretPos = m_lines.GetPos();
if (caretPos > pos) m_lines.SetPos(caretPos + byte_len);
}
MarkAsModified();
return byte_len;
}
wxString EditorCtrl::GetAutoPair(unsigned int pos, const wxString& text) {
const deque<const wxString*> scope = m_syntaxstyler.GetScope(pos);
const map<wxString, wxString> smartPairs = m_syntaxHandler.GetSmartTypingPairs(scope);
const map<wxString, wxString>::const_iterator p = smartPairs.find(text);
#ifdef __WXDEBUG__
unsigned int debug = 0;
if (debug == 1) {
for (map<wxString, wxString>::const_iterator i = smartPairs.begin(); i != smartPairs.end(); ++i)
wxLogDebug(wxT("%s -> %s"), i->first.c_str(), i->second.c_str());
}
#endif
if (p == smartPairs.end()) return wxEmptyString;
return p->second;
}
wxString EditorCtrl::AutoPair(unsigned int pos, const wxString& text, bool addToStack) {
wxASSERT(!text.empty());
if (!m_autopair.Enabled()) return wxEmptyString;
// Are we just before a pair end?
bool inPair = m_autopair.AtEndOfPair(pos);
const unsigned int lineid = m_lines.GetLineFromCharPos(pos);
const unsigned int lineend = m_lines.GetLineEndpos(lineid);
// Only try to autopair if we are before pair-end, ws or eol
if (!inPair && pos != lineend) {
cxLOCKDOC_READ(m_doc)
if (!wxIsspace(doc.GetChar(pos))) return wxEmptyString;
cxENDLOCK
}
const wxString pairEnd = GetAutoPair(pos, text);
if (pairEnd.empty()) return wxEmptyString;
// Only pair equivalent 'brackets' if the total linecount is even
if (text == pairEnd) {
const unsigned int linestart = m_lines.GetLineStartpos(lineid);
if (CountMatchingChars(text[0], linestart, lineend) % 2 != 0) return wxEmptyString;
}
if (addToStack) {
const size_t starter_len = wxConvUTF8.FromWChar(NULL, 0, text) - 1; // also counts trailing null-byte
const size_t ender_len = wxConvUTF8.FromWChar(NULL, 0, pairEnd) - 1; // also counts trailing null-byte
const size_t byte_len = starter_len + ender_len;
// Adjust containing pairs
m_autopair.AdjustEndsUp(byte_len);
const unsigned int pairPos = pos + starter_len;
m_autopair.AddInnerPair(pairPos);
}
return pairEnd;
}
void EditorCtrl::GotoMatchingBracket() {
unsigned int pos = m_lines.GetPos();
const unsigned int len = m_lines.GetLength();
if (pos == len) return;
// Get list of brackets
const deque<const wxString*> scope = m_syntaxstyler.GetScope(pos);
const map<wxString, wxString> smartPairs = m_syntaxHandler.GetSmartTypingPairs(scope);
// Build a set of end brackets
set<wxString> endBrackets;
for (map<wxString, wxString>::const_iterator p2 = smartPairs.begin(); p2 != smartPairs.end(); ++p2) {
endBrackets.insert(p2->second);
}
// Advance until we hit first start or end bracket
bool bracketFound = false;
cxLOCKDOC_READ(m_doc)
for (; pos < len; pos = doc.GetNextCharPos(pos)) {
const wxChar c = doc.GetChar(pos);
// Check if current char is a start bracket
const map<wxString, wxString>::const_iterator p = smartPairs.find(c);
if (p != smartPairs.end()) {
bracketFound = true;
break;
}
// Check if current char is an end bracket
const set<wxString>::const_iterator p2 = endBrackets.find(c);
if (p2 != endBrackets.end()) {
bracketFound = true;
break;
}
}
cxENDLOCK
if (!bracketFound) return;
// Move cursor to the matching bracket
unsigned int pos2;
if (!FindMatchingBracket(pos, pos2)) return;
m_lines.SetPos(pos2);
}
bool EditorCtrl::FindMatchingBracket(unsigned int pos, unsigned int& pos2) {
// Get list of brackets
const deque<const wxString*> scope = m_syntaxstyler.GetScope(pos);
const map<wxString, wxString> smartPairs = m_syntaxHandler.GetSmartTypingPairs(scope);
// Get current char
wxChar c;
cxLOCKDOC_READ(m_doc)
c = doc.GetChar(pos);
cxENDLOCK
bool searchForward = true;
int limit = m_lines.GetLength();
char start_bracket = '\0';
char end_bracket = '\0';
// Check if current char is a start bracket
const map<wxString, wxString>::const_iterator p = smartPairs.find(c);
if (p != smartPairs.end()) {
start_bracket = p->first.mb_str(wxConvUTF8).data()[0];
end_bracket = p->second.mb_str(wxConvUTF8).data()[0];
// Indentical start and end brackets (just search on current line)
if (start_bracket == end_bracket) {
const unsigned int lineid = m_lines.GetCurrentLine();
const unsigned int linestart = m_lines.GetLineStartpos(lineid);
// count brackets from start of line
unsigned int count = 0;
unsigned int bracketpos = 0;
cxLOCKDOC_READ(m_doc)
bool escaped = false;
for (doc_byte_iter dbi(doc, linestart); dbi.GetIndex() < (int)pos; ++dbi) {
if (escaped) escaped = false;
else {
if (*dbi == '\\') escaped = true;
else if (*dbi == start_bracket) {
++count;
bracketpos = dbi.GetIndex();
}
}
}
if (escaped) return false; // current char is escaped
cxENDLOCK
if (count & 1) {
pos2 = bracketpos;
return true;
}
else {
const unsigned int lineend = m_lines.GetLineEndpos(lineid);
cxLOCKDOC_READ(m_doc)
bool escaped = false;
for (doc_byte_iter dbi(doc, pos+1); dbi.GetIndex() < (int)lineend; ++dbi) {
if (escaped) escaped = false;
else {
if (*dbi == '\\') escaped = true;
else if (*dbi == start_bracket) {
pos2 = dbi.GetIndex();
return true;
}
}
}
cxENDLOCK
}
return false;
}
}
else {
// Search through end brackets
bool bracketFound = false;
for (map<wxString, wxString>::const_iterator p2 = smartPairs.begin(); p2 != smartPairs.end(); ++p2) {
if (p2->second == c) {
start_bracket = p2->second.mb_str(wxConvUTF8).data()[0];
end_bracket = p2->first.mb_str(wxConvUTF8).data()[0];
searchForward = false;
bracketFound = true;
limit = 0;
break;
}
}
if (!bracketFound) return false; // no bracket at pos
}
if (searchForward) {
cxLOCKDOC_READ(m_doc)
doc_byte_iter dbi(doc, pos+1);
bool escaped = false;
unsigned int count = 1;
while (dbi.GetIndex() < limit) {
if (escaped) escaped = false;
else {
if (*dbi == '\\') escaped = true;
else if (*dbi == start_bracket) ++count;
else if (*dbi == end_bracket) {
--count;
if (count == 0) {
pos2 = dbi.GetIndex();
return true;
}
}
}
++dbi;
}
cxENDLOCK
}
else {
cxLOCKDOC_READ(m_doc)
doc_byte_iter dbi(doc, pos-1);
unsigned int count = 1;
while (dbi.GetIndex() >= limit) {
if (*dbi == start_bracket) {
unsigned int esc_count = 0;
for (doc_byte_iter dbi2 = dbi-1; dbi2.GetIndex() >= limit && *dbi2 == '\\'; --dbi2) ++esc_count;
if (!(esc_count & 1)) ++count;
}
else if (*dbi == end_bracket) {
unsigned int esc_count = 0;
for (doc_byte_iter dbi2 = dbi-1; dbi2.GetIndex() >= limit && *dbi2 == '\\'; --dbi2) ++esc_count;
if (!(esc_count & 1)) {
--count;
if (count == 0) {
pos2 = dbi.GetIndex();
return true;
}
}
}
--dbi;
}
cxENDLOCK
}
return false;
}
void EditorCtrl::MatchBrackets() {
const unsigned int pos = m_lines.GetPos();
// If no change to document or position, then stop.
if (!m_bracketHighlight.UpdateIfChanged(m_changeToken, pos)) return;
m_bracketHighlight.Clear();
if (pos == m_lines.GetLength()) return;
unsigned int pos2;
if (!FindMatchingBracket(pos, pos2)) return;
if (pos < pos2) m_bracketHighlight.Set(pos, pos2);
else m_bracketHighlight.Set(pos2, pos);
}
unsigned int EditorCtrl::RawDelete(unsigned int start, unsigned int end) {
if (start == end) return 0;
wxASSERT(end > start && end <= m_lines.GetLength());
const unsigned int pos = m_lines.GetPos();
if (m_autopair.HasPairs()) {
const interval& iv = m_autopair.InnerPair();
// Detect backspacing in active auto-pair
if (end == iv.start && end == iv.end) {
unsigned int nextpos;
cxLOCKDOC_READ(m_doc)
nextpos = doc.GetNextCharPos(iv.end);
cxENDLOCK
// Also delete pair ender
end = nextpos;
m_autopair.DropInnerPair();
}
else if (iv.start > start || iv.end < end) {
// Reset autoPair state if deleting outside inner pair
m_autopair.Clear();
}
}
cxLOCKDOC_WRITE(m_doc)
doc.Delete(start, end);
cxENDLOCK
m_lines.Delete(start, end);
// Update stylers
StylersDelete(start, end);
// Update caret pos
unsigned int del_len = end - start;
if (pos > start) {
if (pos <= end) m_lines.SetPos(start);
else m_lines.SetPos(pos - del_len);
}
// Adjust containing pairs
m_autopair.AdjustEndsDown(del_len);
MarkAsModified();
return del_len;
}
void EditorCtrl::RawMove(unsigned int start, unsigned int end, unsigned int dest) {
wxASSERT(start <= end && end <= m_lines.GetLength());
wxASSERT(dest <= start || dest >= end);
wxASSERT(dest <= m_lines.GetLength());
const unsigned int pos = m_lines.GetPos();
if (start < end) {
// Get the text and caret pos
wxString text;
cxLOCKDOC_READ(m_doc)
doc.GetTextPart(start, end, text);
cxENDLOCK
// Delete source
RawDelete(start, end);
// Adjust destination
const unsigned int len = end - start;
if (dest >= end)
dest -= len;
// Insert at destination
RawInsert(dest, text);
}
// If caret was in source, it moves with the text
if (start <= pos && pos <= end)
m_lines.SetPos(dest + (pos - start));
// no need for MarkAsModified(), already called by subfunctions
}
unsigned int EditorCtrl::InsertNewline() {
unsigned int pos = m_lines.GetPos();
const unsigned int lineid = m_lines.GetCurrentLine();
const bool atLineEnd = (pos == m_lines.GetLineEndpos(lineid, true));
// Insert the newline
unsigned int byte_len = RawInsert(pos, wxT("\n"), true);
pos += byte_len;
const wxString indent = GetNewIndentAfterNewline(lineid);
// Check if indentation level should be increased
if (!indent.empty()) {
const unsigned int indent_len = RawInsert(pos, indent, false);
pos += indent_len;
byte_len += indent_len;
// Check there is a line rest that should be moved to new line
if (!atLineEnd) {
// Get the correct indentation for new line
const wxString newindent = GetRealIndent(lineid+1);
const unsigned int indentlevel = CountTextIndent(indent, m_tabWidth);
const unsigned int newindentlevel = CountTextIndent(newindent, m_tabWidth);
// Only double the newlines if the new line will be de-dented
if (newindentlevel < indentlevel) {
const unsigned int newlinelen = RawInsert(pos, wxT('\n'), false);
byte_len += newlinelen;
const unsigned int newlinestart = pos + newlinelen;
// Set correct indentation for new line
if (!newindent.empty())
byte_len += RawInsert(newlinestart, newindent, false);
}
}
}
m_lines.SetPos(pos);
lastpos = pos;
lastaction = ACTION_INSERT;
MarkAsModified();
return byte_len;
}
void EditorCtrl::InsertChar(const wxChar& text) {
if (m_macro.IsRecording()) {
if (text == '\n')
m_macro.Add(wxT("InsertNewline"));
else {
eMacroCmd& cmd = lastaction != ACTION_INSERT || m_macro.IsEmpty() || m_macro.Last().GetName() != wxT("InsertChars")
? m_macro.Add(wxT("InsertChars"), wxT("text"), wxT("")) : m_macro.Last();
cmd.ExtendString(0, text);
}
}
if (m_snippetHandler.IsActive()) {
m_snippetHandler.Insert(wxString(text));
return;
}
// Check if we need to freeze last change
unsigned int pos = m_lines.GetPos();
if (pos != lastpos || lastaction != ACTION_INSERT) {
cxLOCKDOC_WRITE(m_doc)
doc.Freeze();
cxENDLOCK
}
if (m_lines.IsSelected()) {
// Don't freeze if we are inserting a single char over a
// single selection
if (m_lines.GetSelections().size() == 1) do_freeze = false;
InsertOverSelections(wxString(text));
do_freeze = true;
}
else {
// Newline needs special handling for indentation
if (text == wxT('\n')) {
InsertNewline();
return;
}
const unsigned int lineid = m_lines.GetCurrentLine();
unsigned int lineend = m_lines.GetLineEndpos(lineid, true);
const bool atLineEnd = (pos == lineend);
// Insert the char
RawInsert(pos, text, true);
pos = m_lines.GetPos();
if (atLineEnd && text != wxT('\t') && text != wxT(' ')) {
// We only check for indentation change when inserting at end of line
// this makes it possible to manually adjust indentation.
const unsigned int linestart = m_lines.GetLineStartpos(lineid);
lineend = pos;
// Check if indentation level should be changed
if (linestart < lineend) {
int indentChange = 0;
// Check if we should decrease indent
const deque<const wxString*> scope = m_syntaxstyler.GetScope(linestart);
const wxString& decreasePattern = m_syntaxHandler.GetIndentDecreasePattern(scope);
if (!decreasePattern.empty()) {
const search_result res = RegExFind(decreasePattern, linestart, false, NULL, lineend);
if (res.error_code > 0) indentChange = -1;
}
// Check if we should increase indent
// (if line above is a nextlineindenter and this one is not)
if (indentChange == 0 && lineid > 0) {
const wxString& nextPattern = m_syntaxHandler.GetIndentNextPattern(scope);
if (!nextPattern.empty()) {
const unsigned int linestart2 = m_lines.GetLineStartpos(lineid-1);
const unsigned int lineend2 = m_lines.GetLineEndpos(lineid-1, true);
const search_result res = RegExFind(nextPattern, linestart2, false, NULL, lineend2);
if (res.error_code > 0) {
const search_result res2 = RegExFind(nextPattern, linestart, false, NULL, lineend);
if (res2.error_code <= 0) indentChange = 1;
}
}
}
if (indentChange != 0) {
const unsigned int tabWidth = m_tabWidth;
// Get the indentation len based on the line above
const wxString currentIndent = lineid == 0 ? *wxEmptyString : GetNewIndentAfterNewline(lineid-1);
int indentLen = 0;
unsigned int spaces = 0;
for (unsigned int i = 0; i < currentIndent.size(); ++i) {
if (currentIndent[i] == wxT('\t')) {
if (spaces == 0) ++indentLen;
else break; // spaces smaller than tabWidth ends indent
}
else if (currentIndent[i] == wxT(' ')) {
++spaces;
if (spaces == tabWidth) {
++indentLen;
spaces = 0;
}
}
else break;
}
// Adjust indent level
indentLen = wxMax(indentLen + indentChange, 0);
// Count len of current indentation
unsigned int curLen = 0;
spaces = 0;
cxLOCKDOC_READ(m_doc)
for (doc_byte_iter dbi(doc, linestart); (unsigned int)dbi.GetIndex() < lineend; ++dbi) {
if (*dbi == '\t') {
if (spaces == 0) ++curLen;
else break; // spaces smaller than tabWidth ends indent
}
else if (*dbi == ' ') {
++spaces;
if (spaces == tabWidth) {
++curLen;
spaces = 0;
}
}
else break;
}
cxENDLOCK
// Adjust indentation
const int indent_adj = indentLen - curLen;
if (indent_adj > 0) {
wxString indent;
for (unsigned int ia = 0; (int)ia < indent_adj; ++ia) indent += m_indent;
unsigned int indent_len;
cxLOCKDOC_WRITE(m_doc)
indent_len = doc.Insert(linestart, indent);
cxENDLOCK
m_lines.Insert(linestart, indent_len);
StylersInsert(linestart, indent_len);
pos += indent_len;
}
else if (indent_adj < 0) {
unsigned int end = 0;
// Calc how much should be deleted
spaces = 0;
cxLOCKDOC_READ(m_doc)
doc_byte_iter dbi(doc, linestart);
for (; (unsigned int)dbi.GetIndex() < lineend; ++dbi) {
if (*dbi == '\t') {
if (spaces == 0) ++end;
else break; // spaces smaller than tabWidth ends indent
}
else if (*dbi == ' ') {
++spaces;
if (spaces == tabWidth) {
++end;
spaces = 0;
}
}
else break;
if ((int)end == -indent_adj) {
++dbi;
break;
}
}
end = dbi.GetIndex();
cxENDLOCK
if (linestart < end) {
cxLOCKDOC_WRITE(m_doc)
doc.Delete(linestart, end);
cxENDLOCK
m_lines.Delete(linestart, end);
StylersDelete(linestart, end);
pos -= (end - linestart);
}
}
}
}
}
m_lines.SetPos(pos);
}
lastpos = m_lines.GetPos();
lastaction = ACTION_INSERT;
MarkAsModified();
}
void EditorCtrl::Insert(const wxString& text) {
if (text.empty()) return;
if (m_snippetHandler.IsActive()) {
m_snippetHandler.Insert(text);
return;
}
m_lines.Verify();
if (m_lines.IsSelected()) InsertOverSelections(text);
else {
unsigned int pos = m_lines.GetPos();
// Update the database
unsigned int byte_len;
cxLOCKDOC_WRITE(m_doc)
byte_len = doc.Insert(pos, text);
cxENDLOCK
// Update the view
m_lines.Insert(pos, byte_len);
// Update stylers
StylersInsert(pos, byte_len);
// Adjust containing pairs
m_autopair.AdjustEndsUp(byte_len);
// Make sure the caret is at the right position
pos += byte_len;
m_lines.SetPos(pos);
// Draw the updated view
DrawLayout();
}
MarkAsModified();
}
void EditorCtrl::Delete(unsigned int start, unsigned int end) {
wxASSERT(end >= start && end <= m_lines.GetLength());
if (start == end) return;
m_lines.Verify(true);
if (m_snippetHandler.IsActive()) {
m_snippetHandler.Delete(start, end);
return;
}
m_autopair.Clear(); // invalidate auto-pair state
const unsigned int pos = m_lines.GetPos();
cxLOCKDOC_WRITE(m_doc)
doc.Delete(start, end);
cxENDLOCK
m_lines.Delete(start, end);
// Update stylers
StylersDelete(start, end);
// Update caret pos
unsigned int del_len = end - start;
if (pos > start) {
if (pos <= end) m_lines.SetPos(start);
else m_lines.SetPos(pos - del_len);
}
MarkAsModified();
}
void EditorCtrl::Delete(bool delWord) {
if (m_macro.IsRecording()) {
if (delWord) m_macro.Add(wxT("DeleteWord"));
else m_macro.Add(wxT("Delete"));
}
const unsigned int pos = m_lines.GetPos();
// Reset autoPair state if deleting outside inner pair
if (m_autopair.HasPairs())
{
const interval& inner_pair = m_autopair.InnerPair();
if (pos < inner_pair.start || inner_pair.end <= pos)
m_autopair.Clear();
}
// Check if we should delete entire word
if (delWord && !m_lines.IsSelected()) {
const interval iv = GetWordIv(pos);
if (pos >= iv.start && pos < iv.end) {
Delete(pos, iv.end);
return;
}
}
// Check if we delete outside zero-width selection
if (m_lines.IsSelected()) {
const vector<interval>& sels = m_lines.GetSelections();
if (sels.size() == 1 && sels[0].start == sels[0].end) {
m_lines.RemoveAllSelections();
}
}
// Handle deletions in snippet
if (m_snippetHandler.IsActive()) {
if (m_lines.IsSelected()) DeleteSelections();
else if (pos < m_lines.GetLength()) {
unsigned int nextcharpos;
cxLOCKDOC_READ(m_doc)
nextcharpos = doc.GetNextCharPos(pos);
cxENDLOCK
m_snippetHandler.Delete(pos, nextcharpos);
}
return;
}
if (pos != lastpos || lastaction != ACTION_DELETE) {
cxLOCKDOC_WRITE(m_doc)
doc.Freeze();
cxENDLOCK
}
if (m_lines.IsSelected()) {
if (m_lines.IsSelectionShadow()) {
if (DeleteInShadow(pos, true)) return; // if false we have to del outside shadow
}
else {
DeleteSelections();
cxLOCKDOC_WRITE(m_doc)
doc.Freeze();
cxENDLOCK
return;
}
m_lines.RemoveAllSelections();
}
if (pos >= m_lines.GetLength()) return; // Can't delete at end of text
unsigned int nextpos;
wxChar nextchar;
cxLOCKDOC_READ(m_doc)
nextpos = doc.GetNextCharPos(pos);
nextchar = doc.GetChar(pos);
cxENDLOCK
// Check if we are deleting over a fold
unsigned int fold_end;
if (IsPosInFold(nextpos, NULL, &fold_end)) {
Delete(pos, fold_end); // delete fold
return;
}
// Check if we are at a soft tabpoint
const unsigned int tabWidth = m_tabWidth;
if (nextchar == wxT(' ') && m_lines.IsAtTabPoint()
&& pos + tabWidth <= m_lines.GetLength() && IsSpaces(pos, pos + tabWidth))
{
Delete(pos, pos + tabWidth);
}
else {
// Just delete the char
unsigned int byte_len;
cxLOCKDOC_WRITE(m_doc)
byte_len = doc.DeleteChar(pos);
cxENDLOCK
unsigned int del_end = pos + byte_len;
m_lines.Delete(pos, del_end);
StylersDelete(pos, del_end);
MarkAsModified();
}
lastpos = pos;
lastaction = ACTION_DELETE;
}
void EditorCtrl::Backspace(bool delWord) {
if (m_macro.IsRecording()) {
if (delWord) m_macro.Add(wxT("DeleteWord"));
else m_macro.Add(wxT("Backspace"));
}
const unsigned int pos = m_lines.GetPos();
// Check if we should delete entire word
if (delWord && !m_lines.IsSelected()) {
const interval iv = GetWordIv(pos);
if (pos <= iv.end && pos > iv.start) {
Delete(iv.start, pos);
return;
}
}
// Check if we delete outside zero-width selection
if (m_lines.IsSelected()) {
const vector<interval>& sels = m_lines.GetSelections();
if (sels.size() == 1 && sels[0].start == sels[0].end) {
m_lines.RemoveAllSelections();
}
}
// Handle deletions in snippet
if (m_snippetHandler.IsActive()) {
if (m_lines.IsSelected()) DeleteSelections();
else if (pos > 0) {
unsigned int prevcharpos;
cxLOCKDOC_READ(m_doc)
prevcharpos = doc.GetPrevCharPos(pos);
cxENDLOCK
m_snippetHandler.Delete(prevcharpos, pos);
}
return;
}
if (pos != lastpos || lastaction != ACTION_DELETE) {
cxLOCKDOC_WRITE(m_doc)
doc.Freeze();
cxENDLOCK
}
if (m_lines.IsSelected()) {
if (m_lines.IsSelectionShadow()) {
if (DeleteInShadow(pos, false)) return; // if false we have to del outside shadow
}
else {
DeleteSelections();
cxLOCKDOC_WRITE(m_doc)
doc.Freeze();
cxENDLOCK
return;
}
m_lines.RemoveAllSelections();
}
if (pos == 0) return; // Can't delete at start of text
unsigned int prevpos;
wxChar prevchar;
cxLOCKDOC_READ(m_doc)
prevpos = doc.GetPrevCharPos(pos);
prevchar = doc.GetChar(prevpos);
cxENDLOCK
const unsigned int tabWidth = m_tabWidth;
unsigned int newpos;
// Check if we are at a soft tabpoint
if (prevchar == wxT(' ') && m_lines.IsAtTabPoint()
&& pos >= tabWidth && IsSpaces(pos - tabWidth, pos)) {
newpos = pos - tabWidth;
RawDelete(newpos, pos);
}
else {
// Else just delete the char
RawDelete(prevpos, pos);
newpos = prevpos;
}
m_lines.SetPos(newpos);
lastpos = newpos;
lastaction = ACTION_DELETE;
MarkAsModified();
}
void EditorCtrl::Commit(const wxString& label, const wxString& desc) {
cxLOCKDOC_WRITE(m_doc)
doc.Commit(label, desc);
cxENDLOCK
}
void EditorCtrl::Clear() {
cxLOCKDOC_WRITE(m_doc)
doc.Clear();
cxENDLOCK
m_lines.Clear();
scrollPos = 0;
topline = -1;
m_currentSel = -1;
// Draw the updated view
DrawLayout();
MarkAsModified();
}
bool EditorCtrl::IsEmpty() const {
if (!m_path.IsOk()) {
cxLOCKDOC_READ(m_doc)
if (doc.IsEmpty()) return true;
cxENDLOCK
}
return false;
}
cxFileResult EditorCtrl::LoadText(const wxFileName& newpath) {
// Check if we already have set an encoding
wxFontEncoding enc;
cxLOCKDOC_READ(m_doc)
enc = doc.GetPropertyEncoding();
cxENDLOCK
return LoadText(newpath.GetFullPath(), enc);
}
cxFileResult EditorCtrl::LoadText(const wxString& newpath, const RemoteProfile* rp) {
// Check if we already have set an encoding
wxFontEncoding enc;
cxLOCKDOC_READ(m_doc)
enc = doc.GetPropertyEncoding();
cxENDLOCK
return LoadText(newpath, enc, rp);
}
cxFileResult EditorCtrl::LoadText(const wxString& newpath, wxFontEncoding enc, const RemoteProfile* rp) {
wxFileName filepath;
cxFileResult result = LoadLinesIntoDocument(newpath, enc, rp, filepath);
if (result != cxFILE_OK) return result;
scrollPos = 0;
topline = -1;
m_currentSel = -1;
m_modSkipState.m_state = EditorCtrl::ModSkipState::SKIP_STATE_ASK;
m_modSkipState.m_date = wxInvalidDateTime;
MarkAsModified();
// re-set the width
// Has to be bigger than zero to avoid problems during redraw.
UpdateEditorWidth();
SetPath(filepath.GetFullPath()); // also updates syntax
return cxFILE_OK;
}
cxFileResult EditorCtrl::LoadLinesIntoDocument(const wxString& whence_to_load, wxFontEncoding enc, const RemoteProfile* rp, wxFileName& localPath) {
// First clean up old remote info (and delete evt. buffer file);
ClearRemoteInfo();
if (!eDocumentPath::IsRemotePath(whence_to_load)) localPath = whence_to_load;
else {
// If the path points to a remote file, we have to download it first.
m_remoteProfile = rp ? rp : m_parentFrame.GetRemoteProfile(whence_to_load, false);
const wxString buffPath = m_parentFrame.DownloadFile(whence_to_load, m_remoteProfile);
if (buffPath.empty()) return cxFILE_DOWNLOAD_ERROR; // download failed
localPath = buffPath;
m_remotePath = whence_to_load;
}
// Invalidate all stylers
StylersInvalidate();
return m_lines.LoadText(localPath, enc, m_remotePath);
}
bool EditorCtrl::SaveText(bool askforpath) {
// We always have to ask for the path if we don't have it
if (!m_path.IsOk()) askforpath = true;
wxFileName filepath;
bool savedLocal = true;
wxString docName;
cxLOCKDOC_WRITE(m_doc)
docName = doc.GetPropertyName();
cxENDLOCK
wxString newpath;
if (m_remotePath.empty()) {
if (!m_path.IsOk()) {
const wxFileName path(m_parentFrame.GetSaveDir(), docName);
newpath = path.GetFullPath();
}
else {
if (m_path.GetFullName() != docName && !docName.empty()) {
// The filename has been changed.
// Probably from reversing to a previous revision
m_path.SetFullName(docName);
}
newpath = m_path.GetFullPath();
}
}
else {
savedLocal = false;
newpath = docName;
}
if (askforpath || newpath.empty()) {
wxFileDialog dlg(this, _T("Save as..."), _T(""), _T(""), EditorFrame::DefaultFileFilters, wxSAVE|wxCHANGE_DIR);
dlg.SetPath( newpath.empty() ? wxString(_("Untitled")) : newpath );
dlg.Centre();
if (dlg.ShowModal() != wxID_OK) return false;
newpath = dlg.GetPath();
if (wxFileExists(newpath)) {
const int answer = wxMessageBox(newpath + _T(" already exists. Do you want to replace it?"), _T("e Warning"), wxICON_QUESTION|wxOK|wxCANCEL);
if (answer != wxOK) return false;
}
// Verify that the path is valid
filepath = newpath;
filepath.MakeAbsolute();
if (!filepath.IsOk()) {
wxMessageBox(filepath.GetFullPath() + _T(" is not a file!"), _T("e Error"), wxICON_ERROR);
return false;
}
if (filepath.IsDir()) {
wxMessageBox(filepath.GetFullPath() + _T(" is a directory, not a file!"), _T("e Error"), wxICON_ERROR);
return false;
}
savedLocal = true;
}
else filepath = m_path; // path to temp buffer file
// Check if file is write protected
if (filepath.FileExists() && !filepath.IsFileWritable()) {
const int answer = wxMessageBox(filepath.GetFullPath() + _T(" is write protected.\nDo you want to save it anyway?"), _T("Save"), wxOK|wxCANCEL|wxICON_QUESTION);
if (answer != wxOK) return false;
if (!eDocumentPath::MakeWritable(newpath)) {
wxMessageBox(_T("Unable to remove write protection!"), _T("e Error"), wxICON_ERROR);
return false;
}
}
// Check if we need to force the native end-of-line
bool forceNativeEOL = false;
bool noAtomic = false;
eSettings& settings = eGetSettings();
settings.GetSettingBool(wxT("force_native_eol"), forceNativeEOL);
settings.GetSettingBool(wxT("disable_atomic_save"), noAtomic);
// Save the text
cxFileResult savedResult;
const wxString realname = m_remotePath.empty() ? wxT("") : docName;
cxLOCKDOC_WRITE(m_doc)
savedResult = doc.SaveText(filepath, forceNativeEOL, realname, false, noAtomic);
cxENDLOCK
const wxString pathStr = filepath.GetFullPath();
switch (savedResult) {
case cxFILE_OK:
// Clean up remote info if file was saved locally
if (!m_remotePath.empty() && savedLocal)
ClearRemoteInfo();
SetPath(pathStr);
MarkAsModified();
m_savedForPreview = true;
break;
case cxFILE_CONV_ERROR:
wxMessageBox(_T("Could not convert file: ") + pathStr + _T("\nIt may contain chars that cannot be represented in current encoding. Try saving in another encoding."), _T("e Error"), wxICON_ERROR);
return false;
case cxFILE_WRITABLE_ERROR:
wxMessageBox(pathStr + _T(" is write protected."), _T("e Error"), wxICON_ERROR);
return false;
default:
wxMessageBox(_T("Could not save file: ") + pathStr, _T("e Error"), wxICON_ERROR);
return false;
}
if (!m_remotePath.empty()) {
if (!m_parentFrame.UploadFile(m_remotePath, pathStr, m_remoteProfile))
return false;
// Set mirror to new modDate so it matches file on server
const wxDateTime modDate = filepath.GetModificationTime();
const doc_id di = GetDocID();
cxLOCK_WRITE(m_catalyst)
catalyst.SetFileMirror(m_remotePath, di, modDate);
cxENDLOCK
}
return true;
}
void EditorCtrl::SetPath(const wxString& newpath) {
wxASSERT(!eDocumentPath::IsRemotePath(newpath)); // just to catch a bug
if (m_path.GetFullPath() == newpath) return;
m_path = newpath;
wxASSERT(m_path.IsAbsolute());
// Clear the env var cache
m_tmFilePath.clear();
m_tmDirectory.clear();
// Set the syntax to match the new path
if (m_syntaxstyler.UpdateSyntax())
DrawLayout(); // Redraw (since syntax for this file has changed)
}
bool EditorCtrl::IsModified() const {
// When is a document in modified state:
// 1. If it is mirrored but doc_id has changed
if (m_path.IsOk() || IsBundleItem()) {
doc_id di;
wxDateTime modifiedDate;
const wxString mirror = m_remotePath.empty() ? m_path.GetFullPath() : m_remotePath;
bool hasMirror;
cxLOCK_READ(m_catalyst)
hasMirror = catalyst.GetFileMirror(mirror, di, modifiedDate);
cxENDLOCK
if (hasMirror) {
if (modifiedDate.IsValid() && (m_doc == di)) return false;
return true;
}
else {
wxASSERT(false); // cannot have a path and not be mirrored
return true;
}
}
// 2. If it is an un-mirrored but unattached and non-empty draft
cxLOCKDOC_READ(m_doc)
if (doc.IsDraft() && !doc.IsDraftAttached() && !doc.IsEmpty() ) return true;
cxENDLOCK
// If we reach here the doc is un-modified
return false;
}
cxFileResult EditorCtrl::OpenFile(const wxString& filepath, wxFontEncoding enc, const RemoteProfile* rp, const wxString& mate) {
// Bundle items do their own mirror handling during loading
if (eDocumentPath::IsBundlePath(filepath))
return LoadText(filepath, enc, rp);
if (!mate.empty())
SetMate(mate);
// Check if there is a mirror that matches the file
doc_id di;
wxDateTime modifiedDate;
bool isMirror;
cxLOCK_READ(m_catalyst)
isMirror = catalyst.GetFileMirror(filepath, di, modifiedDate);
cxENDLOCK
if(isMirror) {
const bool isRemoteItem = eDocumentPath::IsRemotePath(filepath);
bool doReload = true;
// Check if the file on disk has been changed since last save
if (modifiedDate.IsValid()) {
if (isRemoteItem) {
if (!rp) rp = m_parentFrame.GetRemoteProfile(filepath, false);
const wxDateTime fileDate = m_parentFrame.GetRemoteThread().GetModDate(filepath, *rp);
if (modifiedDate == fileDate)
doReload = false; // No need to reload unchanged file
else wxLogDebug(wxT("file %s needs to be reloaded"), filepath.c_str());
}
else {
const wxFileName path(filepath);
if (path.FileExists() && modifiedDate == path.GetModificationTime())
doReload = false; // No need to reload unchanged file
else wxLogDebug(wxT("file %s needs to be reloaded"), filepath.c_str());
}
}
// Set doc before reload to update
if (filepath != m_path.GetFullPath()) {
if (doReload) EnableRedraw(false); // avoid drawing twice
const bool res = SetDocument(di, filepath, rp);
EnableRedraw(true);
if (!res) return cxFILE_OPEN_ERROR;
}
#ifdef __WXMSW__
// Filename may have changed case on disk
// It gets corrected during reload, but otherwise we have to correct it manually
if (!isRemoteItem && !doReload) {
const wxString longpath = wxFileName(filepath).GetLongPath(); // gets path with correct case
const wxString newName = wxFileName(longpath).GetFullName();
const wxString oldName = GetName();
if (newName != oldName) {
cxLOCKDOC_WRITE(GetDocument())
doc.SetPropertyName(newName);
cxENDLOCK
}
}
#endif
if (!doReload)
return cxFILE_OK;
}
return LoadText(filepath, enc, rp);
}
bool EditorCtrl::SetDocument(const doc_id& di, const wxString& path, const RemoteProfile* rp) {
doc_id oldDoc;
cxLOCKDOC_READ(m_doc)
oldDoc = doc.GetDocument();
cxENDLOCK
if (di == oldDoc) // No reason to set doc if we are already there
return true;
// If the current doc is an clean draft (no revs & no parent)
// we have to remember to delete it after setting the new doc
bool deleteOld = oldDoc.IsOk() && IsEmpty();
topline = -1;
m_currentSel = -1;
m_autopair.Clear(); // reset autoPair state - adamv - changed from 'empty' to 'clear'.
bool inSameHistory = false;
if (oldDoc.IsOk()) {
cxLOCK_READ(m_catalyst)
inSameHistory = catalyst.InSameHistory(oldDoc, di);
cxENDLOCK
}
if (inSameHistory) {
// Reload entire document
cxLOCKDOC_WRITE(m_doc)
doc.SetDocument(di);
cxENDLOCK
ApplyDiff(oldDoc, true /*moveToFirstChange*/);
// If the syntax has been manually set, we want to preserve it
if (!m_syntaxstyler.IsOk()) m_syntaxstyler.UpdateSyntax();
}
else {
// This is a new document, clear up old state
ClearRemoteInfo();
m_path.Clear();
// Invalidate all stylers
// (has to be done before reloading to avoid stale refs)
StylersInvalidate();
cxLOCKDOC_WRITE(m_doc)
doc.SetDocument(di);
cxENDLOCK
// If the path points to a remote file, we have to save it to a temp bufferfile first.
if (eDocumentPath::IsRemotePath(path)) {
m_remoteProfile = rp ? rp : m_parentFrame.GetRemoteProfile(path, false);
m_path = GetAppPaths().CreateTempAppDataFile();
m_remotePath = path;
cxLOCKDOC_WRITE(m_doc)
const cxFileResult res = doc.SaveText(m_path, false, m_remotePath, true); // keep date
if (res != cxFILE_OK) return false;
cxENDLOCK
}
else if (eDocumentPath::IsBundlePath(path)) m_remotePath = path;
else if (!path.empty()) m_path = path;
scrollPos = 0;
m_lines.ReLoadText();
// Set the syntax to match the new filename
m_syntaxstyler.UpdateSyntax();
// re-set the width
UpdateEditorWidth();
}
cxLOCKDOC_WRITE(m_doc)
doc.SetDocRead();
doc.MakeHead(); // Set the current document to be head
cxENDLOCK
if (deleteOld) {
cxLOCK_WRITE(m_catalyst)
catalyst.DeleteDraft(oldDoc);
cxENDLOCK
}
m_lines.Verify(true); // DEBUG
// We mark as modified before redrawing to avoid stale refs i symbol cache
MarkAsModified();
// Draw the updated view
DrawLayout();
SetFocus();
// Update the path (title)
m_parentFrame.UpdateWindowTitle();
// If this is a bundle item we also have to update the panel
UpdateParentPanels();
// Notify that we have changed document
const doc_id docId = di; // just to be on the stack in bug reports
dispatcher.Notify(wxT("WIN_CHANGEDOC"), this, m_parentFrame.GetId());
return true;
}
// Only derived controls are embedded in a parent panel; see BundleItemEditorCtrl.
void EditorCtrl::UpdateParentPanels() {}
void EditorCtrl::GetLinesChangedSince(const doc_id& di, vector<size_t>& lines) {
vector<cxChange> changes;
cxLOCKDOC_READ(m_doc)
const doc_id current = doc.GetDocument();
doc_id prev = di;
if (prev == current) {
if (prev.IsDraft()) prev = doc.GetDraftParent(di.version_id);
else return;
}
changes = doc.GetChanges(prev, current);
cxENDLOCK
const vector<cxLineChange> linechanges = m_lines.ChangesToFullLines(changes);
const size_t end = m_lines.GetLength();
for (vector<cxLineChange>::const_iterator p = linechanges.begin(); p != linechanges.end(); ++p) {
const size_t firstline = m_lines.GetLineFromStartPos(p->start);
const size_t lastline = p->end == end ? m_lines.GetLineCount() : m_lines.GetLineFromStartPos(p->end);
lines.push_back(firstline);
for (size_t i = firstline+1; i < lastline; ++i) {
lines.push_back(i);
}
}
}
void EditorCtrl::ApplyDiff(const doc_id& oldDoc, bool moveToFirstChange) {
vector<cxChange> changes;
cxLOCKDOC_READ(m_doc)
const doc_id di = doc.GetDocument();
changes = doc.GetChanges(oldDoc, di);
cxENDLOCK
// Lines has to be made valid first
m_lines.ApplyDiff(changes);
// When applying changes to the syntax, we have to be very carefull
// not to do any reads of text from stale refs. To avoid this we only
// apply changes as full lines.
const vector<cxLineChange> linechanges = m_lines.ChangesToFullLines(changes);
StylersApplyDiff(changes);
FoldingApplyDiff(linechanges);
if (!changes.empty()) bookmarks.ApplyChanges(changes);
// Move caret to position of first change
if (moveToFirstChange) {
m_lines.RemoveAllSelections();
if (!changes.empty()) {
const unsigned int changePos = changes[0].start;
SetPos(changePos);
wxLogDebug(wxT("changepos: %d"), changePos);
}
if (!IsCaretVisible())
MakeCaretVisibleCenter();
}
}
interval EditorCtrl::UndoSelection(const cxDiffEntry& de) {
wxASSERT(m_lines.IsSelected());
const vector<interval>& selections = m_lines.GetSelections();
if (selections.empty()) return interval(0,0);
if (lastaction != ACTION_UNDOSEL) {
Freeze();
lastaction = ACTION_UNDOSEL;
}
// Delete current selection
interval sel = selections[0];
const bool caretfront = (m_lines.GetPos() == sel.start);
RawDelete(sel.start, sel.end);
unsigned int byte_len = 0;
if (de.range.start < de.range.end) {
// Get the text from target version
wxString text;
cxLOCKDOC_READ(m_doc)
doc_id di = doc.GetDocument();
di.version_id = de.version;
text = doc.GetTextPart(di, de.range.start, de.range.end);
cxENDLOCK
// Insert new text
byte_len = RawInsert(sel.start, text);
}
// Update selection
sel.end = sel.start + byte_len;
m_lines.RemoveAllSelections();
m_lines.AddSelection(sel.start, sel.end);
// Make sure caret follows selection
if (!caretfront) m_lines.SetPos(sel.end);
return sel;
}
void EditorCtrl::StylersClear() {
m_lines.StylersClear();
FoldingClear();
}
void EditorCtrl::StylersInvalidate() {
m_lines.StylersInvalidate();
FoldingClear();
}
void EditorCtrl::StylersInsert(unsigned int pos, unsigned int length) {
m_lines.StylersInsert(pos, length);
FoldingInsert(pos, length);
bookmarks.InsertChars(pos, length);
}
void EditorCtrl::StylersDelete(unsigned int start, unsigned int end) {
m_lines.StylersDelete(start, end);
FoldingDelete(start, end);
bookmarks.DeleteChars(start, end);
}
void EditorCtrl::StylersApplyDiff(vector<cxChange>& changes) {
m_lines.StylersApplyDiff(changes);
}
unsigned int EditorCtrl::GetChangePos(const doc_id& old_version_id) const {
wxASSERT(old_version_id.IsOk());
// Get the diff
doc_id new_version_id;
vector<match> matchlist;
cxLOCKDOC_READ(m_doc)
new_version_id = doc.GetDocument();
wxASSERT(old_version_id != new_version_id);
#ifdef __WXDEBUG__
const Catalyst& catalyst = m_catalyst.GetCatalyst();
wxASSERT(catalyst.InSameHistory(new_version_id, old_version_id));
#endif //__WXDEBUG__
matchlist = doc.Diff(new_version_id, old_version_id);
cxENDLOCK
// Return the position of the first change
if (matchlist.empty()) return 0; // everything changed
if (matchlist[0].iv1_start_pos > 0) return 0; // insertion at top
if (matchlist[0].iv2_start_pos > 0) return 0; // deletion at top
return matchlist[0].iv1_end_pos;
}
void EditorCtrl::RemapPos(const doc_id& old_version_id, unsigned int old_pos, const vector<interval>& old_sel, unsigned int toppos) {
wxASSERT(old_version_id.IsOk());
doc_id new_version_id;
vector<match> matchlist;
cxLOCKDOC_READ(m_doc)
new_version_id = doc.GetDocument();
wxASSERT(old_version_id != new_version_id);
#ifdef __WXDEBUG__
const Catalyst& catalyst = m_catalyst.GetCatalyst();
wxASSERT(catalyst.InSameHistory(new_version_id, old_version_id));
#endif //__WXDEBUG__
matchlist = doc.Diff(new_version_id, old_version_id);
cxENDLOCK
int newpos = -1;
unsigned int current_sel = 0;
int selstart = -1;
int newtoppos = -1;
int prev_end = 0;
unsigned int i = 0;
while (i < matchlist.size()) {
match m = matchlist[i];
// Remap cursor position
if (newpos == -1) {
if (old_pos < m.iv2_start_pos) newpos = prev_end;
else if (old_pos == m.iv2_start_pos) newpos = m.iv1_start_pos;
else if (m.iv2_start_pos < old_pos && old_pos <= m.iv2_end_pos) {
// Translate pos to new pos
const int offset = old_pos - m.iv2_start_pos;
newpos = m.iv1_start_pos + offset;
}
}
// Remap selections
while (current_sel < old_sel.size()) {
interval iv = old_sel[current_sel];
if (selstart == -1) {
if (iv.start <= m.iv2_start_pos) selstart = prev_end;
else if (m.iv2_start_pos < iv.start && iv.start <= m.iv2_end_pos) {
// Translate pos to new pos
const int offset = iv.start - m.iv2_start_pos;
selstart = m.iv1_start_pos + offset;
}
else break;
}
if (selstart != -1) {
int selend = -1;
if (iv.end <= m.iv2_start_pos) selend = prev_end;
else if (m.iv2_start_pos < iv.end && iv.end <= m.iv2_end_pos) {
// Translate pos to new pos
const int offset = iv.end - m.iv2_start_pos;
selend = m.iv1_start_pos + offset;
}
if (selend != -1) {
if (selstart < selend) m_lines.AddSelection(selstart, selend);
selstart = -1;
++current_sel;
}
else break;
}
}
// Remap top pos
if (newtoppos == -1) {
if (toppos <= m.iv2_start_pos) newtoppos = prev_end;
else if (m.iv2_start_pos < toppos && toppos <= m.iv2_end_pos) {
// Translate pos to new pos
const int offset = toppos - m.iv2_start_pos;
newtoppos = m.iv1_start_pos + offset;
}
}
prev_end = m.iv1_end_pos;
++i;
}
wxASSERT(newpos != -1);
wxASSERT(newtoppos != -1);
m_lines.SetPos(newpos);
const wxPoint cp = m_lines.GetCharPos(newtoppos);
scrollPos = cp.y;
}
void EditorCtrl::DeleteSelections() {
const interval* const selection = m_lines.FirstSelection();
if (selection == NULL) return;
if (m_snippetHandler.IsActive()) {
m_snippetHandler.Delete(selection->start, selection->end);
return;
}
m_autopair.Clear(); // invalidate auto-pair state
cxLOCKDOC_WRITE(m_doc)
doc.Freeze(); // always freeze before modifying sel contents
cxENDLOCK
unsigned int pos = m_lines.GetPos();
const vector<interval>& selections = m_lines.GetSelections();
const bool hasRanges = HasSearchRange();
size_t dl = 0;
for (vector<interval>::const_iterator iv = selections.begin(); iv != selections.end(); ++iv) {
if (iv->start == iv->end) continue;
const size_t start = iv->start - dl;
const size_t end = iv->end - dl;
const size_t len = iv->end - iv->start;
cxLOCKDOC_WRITE(m_doc)
doc.Delete(start, end);
cxENDLOCK
m_lines.Delete(start, end);
StylersDelete(start, end);
// Update caret position
if (pos >= start && pos <= end) pos = start;
else if (pos > end) pos -= len;
if (hasRanges) {
// Find range containing deletion
for (size_t i = 0; i < m_searchRanges.size(); ++i) {
interval& r = m_searchRanges[i];
if (end <= r.end) {
// Adjust cursor
unsigned int& c = m_cursors[i];
if (c == end) c = start;
else if (c > end) c -= len;
// Adjust range len
r.end -= len;
// Adjust all following ranges
++i;
for (; i < m_searchRanges.size(); ++i) {
m_searchRanges[i].start -= len;
m_searchRanges[i].end -= len;
m_cursors[i] -= len;
}
break;
}
}
}
dl += len;
}
m_lines.RemoveAllSelections();
m_lines.SetPos(pos);
// We might have been called while a selection is being made
m_currentSel = -1;
MarkAsModified();
}
bool EditorCtrl::DeleteInShadow(unsigned int pos, bool nextchar) {
wxASSERT(m_lines.IsSelectionShadow() && m_lines.IsSelected());
wxASSERT(pos <= m_lines.GetLength());
bool inAutoPair = false;
if (m_autopair.HasPairs()) {
const interval& iv = m_autopair.InnerPair();
// Detect backspacing in active auto-pair
if (!nextchar && iv.IsPoint(pos)) {
// Also delete pair ender
inAutoPair = true;
m_autopair.DropInnerPair();
}
else if ((nextchar && iv.end <= pos) || (!nextchar && pos <= iv.start)) {
// Reset autoPair state if deleting outside inner pair
m_autopair.Clear();
}
}
const vector<interval> selections = m_lines.GetSelections();
unsigned int offset = 0;
// Calculate delete positions
for (vector<interval>::const_iterator iv = selections.begin(); iv != selections.end(); ++iv) {
// Check for overlap
if ( iv->start <= pos && pos <= iv->end) {
// Check if range is outside shadow
if (pos == iv->start && nextchar == false) return false;
if (pos == iv->end && nextchar == true) return false;
cxLOCKDOC_READ(m_doc)
offset = doc.GetLengthInChars(iv->start, pos);
cxENDLOCK
break;
}
}
m_lines.RemoveAllSelections();
m_lines.ShadowSelections(); // We have to restore shadow mode after removing old selections
// Do the deletions
int dl = 0;
for (vector<interval>::const_iterator i = selections.begin(); i != selections.end(); ++i) {
unsigned int dp;
unsigned int byte_len;
unsigned int del_end;
cxLOCKDOC_WRITE(m_doc)
dp = doc.GetCharPos(i->start - dl, offset);
byte_len = doc.DeleteChar(dp, nextchar);
cxENDLOCK
const bool atCaret = (dp == pos);
if (nextchar) {
del_end = dp + byte_len;
m_lines.Delete(dp, del_end);
StylersDelete(dp, del_end);
if (pos > dp) pos -= byte_len; // Update caret position
}
else { // delete previous char
del_end = dp;
unsigned int delPos = dp-byte_len;
if (inAutoPair) {
if (pos == dp) pos -= byte_len; // if current only move pos one back
cxLOCKDOC_WRITE(m_doc)
byte_len += doc.DeleteChar(delPos, true);
cxENDLOCK
m_lines.Delete(delPos, delPos+byte_len);
StylersDelete(delPos, delPos+byte_len);
if (pos > dp) pos -= byte_len; // adjust pos to full deletion
}
else {
m_lines.Delete(delPos, dp);
StylersDelete(delPos, dp);
if (pos >= dp) pos -= byte_len; // Update caret position
}
}
// pairStack may be moved by insertions above it
if (m_autopair.BeforeOuterPair(del_end))
m_autopair.AdjustIntervalsDown(byte_len);
if (atCaret) // Adjust containing pairs
m_autopair.AdjustEndsDown(byte_len);
// Restore shadow selection
m_lines.AddSelection(i->start - dl, (i->end - dl) - byte_len);
dl += byte_len;
}
m_lines.SetPos(pos);
lastpos = pos;
lastaction = ACTION_DELETE;
MarkAsModified();
return true;
}
void EditorCtrl::InsertOverSelections(const wxString& text) {
if(!m_lines.IsSelected()) return;
const vector<interval>& selections = m_lines.GetSelections();
unsigned int pos = m_lines.GetPos();
m_autopair.ClearIfInsertingOutsideInnerPair(pos);
wxString autoPair;
if (text.length() == 1) {
if (m_lines.IsSelectionShadow()) {
if (m_autopair.HasPairs()) {
if (pos != m_autopair.InnerPair().end) {
// Reset autoPair state if inserting outside inner pair
m_autopair.Clear();
}
else {
wxChar c;
cxLOCKDOC_READ(m_doc)
c = doc.GetChar(pos);
cxENDLOCK
// Jump over auto-paired char
if (text == c) {
cxLOCKDOC_READ(m_doc)
pos = doc.GetNextCharPos(pos);
cxENDLOCK
m_lines.SetPos(pos);
m_autopair.DropInnerPair();
return;
}
}
}
}
else if (m_doAutoWrap) {
// if all selections are zero-length, we want to enter multi-edit mode instead
bool doWrap = false;
for (vector<interval>::const_iterator p = selections.begin(); p != selections.end(); ++p) {
if (p->start != p->end) {
doWrap = true;
break;
}
}
if (doWrap) {
const deque<const wxString*> scope = m_syntaxstyler.GetScope(pos);
const map<wxString, wxString> smartPairs = m_syntaxHandler.GetSmartTypingPairs(scope);
map<wxString, wxString>::const_iterator p = smartPairs.find(text);
if (p != smartPairs.end()) {
WrapSelections(p->first, p->second);
return;
}
}
}
// else just do standard autopairing
autoPair = AutoPair(pos, text);
}
unsigned int offset = 0;
unsigned int shadowlength = 0;
vector<unsigned int> inpos;
// Calculate insert positions
if (m_lines.IsSelectionShadow()) {
const unsigned int shadowpos = m_lines.GetPos();
for (vector<interval>::const_iterator iv = selections.begin(); iv != selections.end(); ++iv) {
inpos.push_back(iv->start);
if (shadowpos >= iv->start && shadowpos <= iv->end)
offset = shadowpos - iv->start;
}
// Calculate length of shadow selections (they should all be the same size)
shadowlength = selections[0].end - selections[0].start;
m_lines.RemoveAllSelections();
m_lines.ShadowSelections(); // We have to restore shadow mode
}
else {
unsigned int dl = 0;
for (vector<interval>::const_iterator iv = selections.begin(); iv != selections.end(); ++iv) {
inpos.push_back(iv->start-dl);
dl += iv->end - iv->start;
}
DeleteSelections();
pos = m_lines.GetPos(); // deletions can have changed pos
if (inpos.size() > 1) m_lines.ShadowSelections();
}
// Insert copied text
if (!text.empty()) {
unsigned int il = 0;
for (vector<unsigned int>::const_iterator i = inpos.begin(); i != inpos.end(); ++i) {
const unsigned int ins_pos = *i+il+offset;
unsigned int byte_len;
cxLOCKDOC_WRITE(m_doc)
byte_len = doc.Insert(ins_pos, text);
cxENDLOCK
m_lines.Insert(ins_pos, byte_len);
StylersInsert(ins_pos, byte_len);
const unsigned int pair_pos = ins_pos+byte_len;
unsigned int pair_len = 0;
if (text == wxT("\n")) {
// Check if we also need to insert indentation
const unsigned int lineid = m_lines.GetLineFromCharPos(ins_pos);
const wxString indent = GetNewIndentAfterNewline(lineid);
if (!indent.empty()) {
const unsigned int indent_pos = ins_pos+1;
unsigned int indent_len;
cxLOCKDOC_WRITE(m_doc)
indent_len = doc.Insert(indent_pos, indent);
cxENDLOCK
m_lines.Insert(indent_pos, indent_len);
StylersInsert(indent_pos, indent_len);
byte_len += indent_len;
}
}
else if (!autoPair.empty()) {
cxLOCKDOC_WRITE(m_doc)
pair_len = doc.Insert(pair_pos, autoPair);
cxENDLOCK
m_lines.Insert(pair_pos, pair_len);
StylersInsert(pair_pos, pair_len);
}
// Extend shadow to cover new text
const unsigned int full_len = byte_len + pair_len;
if (m_lines.IsSelectionShadow()) m_lines.AddSelection(*i+il, *i+il+shadowlength+full_len);
// pairStack may be moved by insertions above it
if (m_autopair.BeforeOuterPair(pair_pos)) {
m_autopair.AdjustIntervalsUp(full_len);
}
// Adjust caret pos
if (pos > ins_pos) pos += full_len;
else if (pos == ins_pos) {
pos += byte_len;
// If we are just inserting text, adjust containing pairs
if (autoPair.empty())
m_autopair.AdjustEndsUp(byte_len);
}
il += full_len;
}
}
m_lines.SetPos(pos);
if (do_freeze && !m_lines.IsSelectionShadow()) {
cxLOCKDOC_WRITE(m_doc)
doc.Freeze();
cxENDLOCK
}
MarkAsModified();
}
void EditorCtrl::WrapSelections(const wxString& front, const wxString& back) {
const vector<interval>& selections = m_lines.GetSelections();
unsigned int pos = m_lines.GetPos();
unsigned int offset = 0;
for (vector<interval>::const_iterator p = selections.begin(); p != selections.end(); ++p) {
//offset += RawInsert(offset + p->start, front);
//offset += RawInsert(offset + p->end, back);
unsigned int insPos = offset + p->start;
unsigned int byte_len = RawInsert(insPos, front);
if (pos > insPos) pos += byte_len;
offset += byte_len;
insPos = offset + p->end;
byte_len = RawInsert(insPos, back);
if (pos >= insPos) pos += byte_len;
offset += byte_len;
}
m_lines.SetPos(pos);
// Selection state
m_lines.RemoveAllSelections();
m_currentSel = -1;
m_sel_start = pos;
}
vector<unsigned int> EditorCtrl::GetSelectedLineNumbers() {
vector<unsigned int> sel_lines;
const vector<interval>& selections = m_lines.GetSelections();
unsigned int prevline = m_lines.GetLineFromCharPos(selections.begin()->start);
sel_lines.push_back(prevline);
// Get a list of lines with selections
for (vector<interval>::const_iterator iv = selections.begin(); iv != selections.end(); ++iv) {
unsigned int startline = m_lines.GetLineFromCharPos(iv->start);
unsigned int endline = m_lines.GetLineFromCharPos(iv->end);
if (iv->end == m_lines.GetLineStartpos(endline)) --endline;
for (; startline <= endline; ++startline) {
if (startline > prevline) { // avoid duplicates
sel_lines.push_back(startline);
prevline = startline;
}
}
}
return sel_lines;
}
void EditorCtrl::SelectLines(const vector<unsigned int>& sel_lines) {
unsigned int lastline = 0;
unsigned int firststart = 0;
int sel = -1;
// select the lines (consequtive lines as one selection)
for (vector<unsigned int>::const_iterator i = sel_lines.begin(); i != sel_lines.end(); ++i) {
unsigned int linestart, lineend;
m_lines.GetLineExtent(*i, linestart, lineend);
if (i > sel_lines.begin() && *i == lastline+1 && sel != -1)
m_lines.UpdateSelection(sel, firststart, lineend);
else {
sel = m_lines.AddSelection(linestart, lineend);
firststart = linestart;
}
lastline = *i;
}
}
void EditorCtrl::TabsToSpaces() {
if (GetLength() == 0) return; // Cannot do anything in empty doc
wxBusyCursor busy;
const unsigned int tabWidth = m_tabWidth;
const wxString indentString(wxT(' '), tabWidth);
unsigned int pos = m_lines.GetPos();
if (m_lines.IsSelected()) {
// Get list of lines to be converted
vector<unsigned int> sel_lines = GetSelectedLineNumbers();
m_lines.RemoveAllSelections();
bool reSelect = true;
Freeze();
cxLOCKDOC_WRITE(m_doc)
doc_byte_iter dbi(doc, 0);
for (vector<unsigned int>::const_iterator p = sel_lines.begin(); p != sel_lines.end(); ++p) {
const unsigned int linestart = m_lines.GetLineStartpos(*p);
unsigned int lineend = m_lines.GetLineEndpos(*p, true);
if (linestart == lineend) continue;
unsigned int indent = 0;
dbi.SetIndex(linestart);
while ((unsigned int)dbi.GetIndex() < lineend) {
if (*dbi == '\t') {
// it is ok to have a few spaces before tab (making one mixed tab)
const unsigned int spaces = tabWidth - (indent % tabWidth);
indent += spaces;
// Replace the tab char
const unsigned int tabPos = dbi.GetIndex();
RawDelete(tabPos, tabPos+1);
const unsigned int byte_len = RawInsert(tabPos, ((spaces == tabWidth) ? indentString : wxString(wxT(' '), spaces)) );
dbi.RefreshIndex(tabPos + byte_len);
if (byte_len > 1) {
const unsigned int diff = byte_len - 1;
lineend += diff; // line length may have changed
if (pos > tabPos) pos += diff;
}
}
else if (*dbi == ' ') {
++indent;
++dbi;
}
else break;
}
}
cxENDLOCK
Freeze();
// Re-select the lines (consequtive lines as one selection)
if (reSelect && !sel_lines.empty()) {
SelectLines(sel_lines);
m_lines.SetPos(m_lines.GetLineEndpos(sel_lines.back(), false));
}
}
else {
// Convert entire document
Freeze();
cxLOCKDOC_WRITE(m_doc)
doc_byte_iter dbi(doc, 0);
doc.StartChange();
int len = doc.GetLength();
do {
unsigned int indent = 0;
while (dbi.GetIndex() < len) {
if (*dbi == '\t') {
// it is ok to have a few spaces before tab (making one mixed tab)
const unsigned int spaces = tabWidth - (indent % tabWidth);
indent += spaces;
// Replace the tab char
const unsigned int tabPos = dbi.GetIndex();
const unsigned int byte_len = doc.Replace(tabPos, tabPos+1, ((spaces == tabWidth) ? indentString : wxString(wxT(' '), spaces)));
dbi.RefreshIndex(tabPos + byte_len);
if (pos > tabPos) pos += (byte_len-1);
len = doc.GetLength(); // update after change
}
else if (*dbi == ' ') {
++indent;
++dbi;
}
else break;
}
// Advance to next line
while (dbi.GetIndex() < len) {
const char c = *dbi;
++dbi;
if (c ==