📄 document.cxx
字号:
// Scintilla source code edit control
/** @file Document.cxx
** Text document that handles notifications, DBCS, styling, words and end of line.
**/
// Copyright 1998-2003 by Neil Hodgson <neilh@scintilla.org>
// The License.txt file describes the conditions under which this software may be distributed.
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include "Platform.h"
#include "Scintilla.h"
#include "SVector.h"
#include "SplitVector.h"
#include "Partitioning.h"
#include "CellBuffer.h"
#include "CharClassify.h"
#include "Document.h"
#include "RESearch.h"
// This is ASCII specific but is safe with chars >= 0x80
static inline bool isspacechar(unsigned char ch) {
return (ch == ' ') || ((ch >= 0x09) && (ch <= 0x0d));
}
static inline bool IsPunctuation(char ch) {
return isascii(ch) && ispunct(ch);
}
static inline bool IsADigit(char ch) {
return isascii(ch) && isdigit(ch);
}
static inline bool IsLowerCase(char ch) {
return isascii(ch) && islower(ch);
}
static inline bool IsUpperCase(char ch) {
return isascii(ch) && isupper(ch);
}
Document::Document() {
refCount = 0;
#ifdef unix
eolMode = SC_EOL_LF;
#else
eolMode = SC_EOL_CRLF;
#endif
dbcsCodePage = 0;
stylingBits = 5;
stylingBitsMask = 0x1F;
stylingMask = 0;
endStyled = 0;
styleClock = 0;
enteredCount = 0;
enteredReadOnlyCount = 0;
tabInChars = 8;
indentInChars = 0;
actualIndentInChars = 8;
useTabs = true;
tabIndents = true;
backspaceUnindents = false;
watchers = 0;
lenWatchers = 0;
matchesValid = false;
pre = 0;
substituted = 0;
}
Document::~Document() {
for (int i = 0; i < lenWatchers; i++) {
watchers[i].watcher->NotifyDeleted(this, watchers[i].userData);
}
delete []watchers;
watchers = 0;
lenWatchers = 0;
delete pre;
pre = 0;
delete []substituted;
substituted = 0;
}
// Increase reference count and return its previous value.
int Document::AddRef() {
return refCount++;
}
// Decrease reference count and return its previous value.
// Delete the document if reference count reaches zero.
int Document::Release() {
int curRefCount = --refCount;
if (curRefCount == 0)
delete this;
return curRefCount;
}
void Document::SetSavePoint() {
cb.SetSavePoint();
NotifySavePoint(true);
}
int Document::AddMark(int line, int markerNum) {
int prev = cb.AddMark(line, markerNum);
DocModification mh(SC_MOD_CHANGEMARKER, LineStart(line), 0, 0, 0, line);
mh.line = line;
NotifyModified(mh);
return prev;
}
void Document::AddMarkSet(int line, int valueSet) {
unsigned int m = valueSet;
for (int i = 0; m; i++, m >>= 1)
if (m & 1)
cb.AddMark(line, i);
DocModification mh(SC_MOD_CHANGEMARKER, LineStart(line), 0, 0, 0, line);
mh.line = line;
NotifyModified(mh);
}
void Document::DeleteMark(int line, int markerNum) {
cb.DeleteMark(line, markerNum);
DocModification mh(SC_MOD_CHANGEMARKER, LineStart(line), 0, 0, 0, line);
mh.line = line;
NotifyModified(mh);
}
void Document::DeleteMarkFromHandle(int markerHandle) {
cb.DeleteMarkFromHandle(markerHandle);
DocModification mh(SC_MOD_CHANGEMARKER, 0, 0, 0, 0);
mh.line = -1;
NotifyModified(mh);
}
void Document::DeleteAllMarks(int markerNum) {
cb.DeleteAllMarks(markerNum);
DocModification mh(SC_MOD_CHANGEMARKER, 0, 0, 0, 0);
mh.line = -1;
NotifyModified(mh);
}
int Document::LineStart(int line) {
return cb.LineStart(line);
}
int Document::LineEnd(int line) {
if (line == LinesTotal() - 1) {
return LineStart(line + 1);
} else {
int position = LineStart(line + 1) - 1;
// When line terminator is CR+LF, may need to go back one more
if ((position > LineStart(line)) && (cb.CharAt(position - 1) == '\r')) {
position--;
}
return position;
}
}
int Document::LineFromPosition(int pos) {
return cb.LineFromPosition(pos);
}
int Document::LineEndPosition(int position) {
return LineEnd(LineFromPosition(position));
}
int Document::VCHomePosition(int position) {
int line = LineFromPosition(position);
int startPosition = LineStart(line);
int endLine = LineStart(line + 1) - 1;
int startText = startPosition;
while (startText < endLine && (cb.CharAt(startText) == ' ' || cb.CharAt(startText) == '\t' ) )
startText++;
if (position == startText)
return startPosition;
else
return startText;
}
int Document::SetLevel(int line, int level) {
int prev = cb.SetLevel(line, level);
if (prev != level) {
DocModification mh(SC_MOD_CHANGEFOLD | SC_MOD_CHANGEMARKER,
LineStart(line), 0, 0, 0);
mh.line = line;
mh.foldLevelNow = level;
mh.foldLevelPrev = prev;
NotifyModified(mh);
}
return prev;
}
static bool IsSubordinate(int levelStart, int levelTry) {
if (levelTry & SC_FOLDLEVELWHITEFLAG)
return true;
else
return (levelStart & SC_FOLDLEVELNUMBERMASK) < (levelTry & SC_FOLDLEVELNUMBERMASK);
}
int Document::GetLastChild(int lineParent, int level) {
if (level == -1)
level = GetLevel(lineParent) & SC_FOLDLEVELNUMBERMASK;
int maxLine = LinesTotal();
int lineMaxSubord = lineParent;
while (lineMaxSubord < maxLine - 1) {
EnsureStyledTo(LineStart(lineMaxSubord + 2));
if (!IsSubordinate(level, GetLevel(lineMaxSubord + 1)))
break;
lineMaxSubord++;
}
if (lineMaxSubord > lineParent) {
if (level > (GetLevel(lineMaxSubord + 1) & SC_FOLDLEVELNUMBERMASK)) {
// Have chewed up some whitespace that belongs to a parent so seek back
if (GetLevel(lineMaxSubord) & SC_FOLDLEVELWHITEFLAG) {
lineMaxSubord--;
}
}
}
return lineMaxSubord;
}
int Document::GetFoldParent(int line) {
int level = GetLevel(line) & SC_FOLDLEVELNUMBERMASK;
int lineLook = line - 1;
while ((lineLook > 0) && (
(!(GetLevel(lineLook) & SC_FOLDLEVELHEADERFLAG)) ||
((GetLevel(lineLook) & SC_FOLDLEVELNUMBERMASK) >= level))
) {
lineLook--;
}
if ((GetLevel(lineLook) & SC_FOLDLEVELHEADERFLAG) &&
((GetLevel(lineLook) & SC_FOLDLEVELNUMBERMASK) < level)) {
return lineLook;
} else {
return -1;
}
}
int Document::ClampPositionIntoDocument(int pos) {
return Platform::Clamp(pos, 0, Length());
}
bool Document::IsCrLf(int pos) {
if (pos < 0)
return false;
if (pos >= (Length() - 1))
return false;
return (cb.CharAt(pos) == '\r') && (cb.CharAt(pos + 1) == '\n');
}
static const int maxBytesInDBCSCharacter=5;
int Document::LenChar(int pos) {
if (pos < 0) {
return 1;
} else if (IsCrLf(pos)) {
return 2;
} else if (SC_CP_UTF8 == dbcsCodePage) {
unsigned char ch = static_cast<unsigned char>(cb.CharAt(pos));
if (ch < 0x80)
return 1;
int len = 2;
if (ch >= (0x80 + 0x40 + 0x20))
len = 3;
int lengthDoc = Length();
if ((pos + len) > lengthDoc)
return lengthDoc -pos;
else
return len;
} else if (dbcsCodePage) {
char mbstr[maxBytesInDBCSCharacter+1];
int i;
for (i=0; i<Platform::DBCSCharMaxLength(); i++) {
mbstr[i] = cb.CharAt(pos+i);
}
mbstr[i] = '\0';
return Platform::DBCSCharLength(dbcsCodePage, mbstr);
} else {
return 1;
}
}
// Normalise a position so that it is not halfway through a two byte character.
// This can occur in two situations -
// When lines are terminated with \r\n pairs which should be treated as one character.
// When displaying DBCS text such as Japanese.
// If moving, move the position in the indicated direction.
int Document::MovePositionOutsideChar(int pos, int moveDir, bool checkLineEnd) {
//Platform::DebugPrintf("NoCRLF %d %d\n", pos, moveDir);
// If out of range, just return minimum/maximum value.
if (pos <= 0)
return 0;
if (pos >= Length())
return Length();
// PLATFORM_ASSERT(pos > 0 && pos < Length());
if (checkLineEnd && IsCrLf(pos - 1)) {
if (moveDir > 0)
return pos + 1;
else
return pos - 1;
}
// Not between CR and LF
if (dbcsCodePage) {
if (SC_CP_UTF8 == dbcsCodePage) {
unsigned char ch = static_cast<unsigned char>(cb.CharAt(pos));
while ((pos > 0) && (pos < Length()) && (ch >= 0x80) && (ch < (0x80 + 0x40))) {
// ch is a trail byte
if (moveDir > 0)
pos++;
else
pos--;
ch = static_cast<unsigned char>(cb.CharAt(pos));
}
} else {
// Anchor DBCS calculations at start of line because start of line can
// not be a DBCS trail byte.
int posCheck = LineStart(LineFromPosition(pos));
while (posCheck < pos) {
char mbstr[maxBytesInDBCSCharacter+1];
int i;
for(i=0;i<Platform::DBCSCharMaxLength();i++) {
mbstr[i] = cb.CharAt(posCheck+i);
}
mbstr[i] = '\0';
int mbsize = Platform::DBCSCharLength(dbcsCodePage, mbstr);
if (posCheck + mbsize == pos) {
return pos;
} else if (posCheck + mbsize > pos) {
if (moveDir > 0) {
return posCheck + mbsize;
} else {
return posCheck;
}
}
posCheck += mbsize;
}
}
}
return pos;
}
void Document::ModifiedAt(int pos) {
if (endStyled > pos)
endStyled = pos;
}
void Document::CheckReadOnly() {
if (cb.IsReadOnly() && enteredReadOnlyCount == 0) {
enteredReadOnlyCount++;
NotifyModifyAttempt();
enteredReadOnlyCount--;
}
}
// Document only modified by gateways DeleteChars, InsertString, Undo, Redo, and SetStyleAt.
// SetStyleAt does not change the persistent state of a document
bool Document::DeleteChars(int pos, int len) {
if (len == 0)
return false;
if ((pos + len) > Length())
return false;
CheckReadOnly();
if (enteredCount != 0) {
return false;
} else {
enteredCount++;
if (!cb.IsReadOnly()) {
NotifyModified(
DocModification(
SC_MOD_BEFOREDELETE | SC_PERFORMED_USER,
pos, len,
0, 0));
int prevLinesTotal = LinesTotal();
bool startSavePoint = cb.IsSavePoint();
bool startSequence = false;
const char *text = cb.DeleteChars(pos, len, startSequence);
if (startSavePoint && cb.IsCollectingUndo())
NotifySavePoint(!startSavePoint);
if ((pos < Length()) || (pos == 0))
ModifiedAt(pos);
else
ModifiedAt(pos-1);
NotifyModified(
DocModification(
SC_MOD_DELETETEXT | SC_PERFORMED_USER | (startSequence?SC_STARTACTION:0),
pos, len,
LinesTotal() - prevLinesTotal, text));
}
enteredCount--;
}
return !cb.IsReadOnly();
}
/**
* Insert a string with a length.
*/
bool Document::InsertString(int position, const char *s, int insertLength) {
if (insertLength <= 0) {
return false;
}
CheckReadOnly();
if (enteredCount != 0) {
return false;
} else {
enteredCount++;
if (!cb.IsReadOnly()) {
NotifyModified(
DocModification(
SC_MOD_BEFOREINSERT | SC_PERFORMED_USER,
position, insertLength,
0, s));
int prevLinesTotal = LinesTotal();
bool startSavePoint = cb.IsSavePoint();
bool startSequence = false;
const char *text = cb.InsertString(position, s, insertLength, startSequence);
if (startSavePoint && cb.IsCollectingUndo())
NotifySavePoint(!startSavePoint);
ModifiedAt(position);
NotifyModified(
DocModification(
SC_MOD_INSERTTEXT | SC_PERFORMED_USER | (startSequence?SC_STARTACTION:0),
position, insertLength,
LinesTotal() - prevLinesTotal, text));
}
enteredCount--;
}
return !cb.IsReadOnly();
}
int Document::Undo() {
int newPos = -1;
CheckReadOnly();
if (enteredCount == 0) {
enteredCount++;
if (!cb.IsReadOnly()) {
bool startSavePoint = cb.IsSavePoint();
bool multiLine = false;
int steps = cb.StartUndo();
//Platform::DebugPrintf("Steps=%d\n", steps);
for (int step = 0; step < steps; step++) {
const int prevLinesTotal = LinesTotal();
const Action &action = cb.GetUndoStep();
if (action.at == removeAction) {
NotifyModified(DocModification(
SC_MOD_BEFOREINSERT | SC_PERFORMED_UNDO, action));
} else {
NotifyModified(DocModification(
SC_MOD_BEFOREDELETE | SC_PERFORMED_UNDO, action));
}
cb.PerformUndoStep();
int cellPosition = action.position;
ModifiedAt(cellPosition);
newPos = cellPosition;
int modFlags = SC_PERFORMED_UNDO;
// With undo, an insertion action becomes a deletion notification
if (action.at == removeAction) {
newPos += action.lenData;
modFlags |= SC_MOD_INSERTTEXT;
} else {
modFlags |= SC_MOD_DELETETEXT;
}
if (steps > 1)
modFlags |= SC_MULTISTEPUNDOREDO;
const int linesAdded = LinesTotal() - prevLinesTotal;
if (linesAdded != 0)
multiLine = true;
if (step == steps - 1) {
modFlags |= SC_LASTSTEPINUNDOREDO;
if (multiLine)
modFlags |= SC_MULTILINEUNDOREDO;
}
NotifyModified(DocModification(modFlags, cellPosition, action.lenData,
linesAdded, action.data));
}
bool endSavePoint = cb.IsSavePoint();
if (startSavePoint != endSavePoint)
NotifySavePoint(endSavePoint);
}
enteredCount--;
}
return newPos;
}
int Document::Redo() {
int newPos = -1;
CheckReadOnly();
if (enteredCount == 0) {
enteredCount++;
if (!cb.IsReadOnly()) {
bool startSavePoint = cb.IsSavePoint();
bool multiLine = false;
int steps = cb.StartRedo();
for (int step = 0; step < steps; step++) {
const int prevLinesTotal = LinesTotal();
const Action &action = cb.GetRedoStep();
if (action.at == insertAction) {
NotifyModified(DocModification(
SC_MOD_BEFOREINSERT | SC_PERFORMED_REDO, action));
} else {
NotifyModified(DocModification(
SC_MOD_BEFOREDELETE | SC_PERFORMED_REDO, action));
}
cb.PerformRedoStep();
ModifiedAt(action.position);
newPos = action.position;
int modFlags = SC_PERFORMED_REDO;
if (action.at == insertAction) {
newPos += action.lenData;
modFlags |= SC_MOD_INSERTTEXT;
} else {
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -