aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorNeil <nyamatongwe@gmail.com>2014-08-08 10:01:55 +1000
committerNeil <nyamatongwe@gmail.com>2014-08-08 10:01:55 +1000
commitc5905297575b23f9454cb0dfe574140c2a329c95 (patch)
tree5d1c711c675c9bfddf13a741149e2220c26633fe
parentced6baba095c3ee140c1527080ff68faecab31ca (diff)
downloadscintilla-mirror-c5905297575b23f9454cb0dfe574140c2a329c95.tar.gz
Implement explicit tab stops per line.
From Nick Gravgaard.
-rw-r--r--doc/ScintillaDoc.html15
-rw-r--r--doc/ScintillaHistory.html4
-rw-r--r--include/Scintilla.h6
-rw-r--r--include/Scintilla.iface12
-rw-r--r--src/EditView.cxx60
-rw-r--r--src/EditView.h9
-rw-r--r--src/Editor.cxx27
-rw-r--r--src/PerLine.cxx70
-rw-r--r--src/PerLine.h17
-rw-r--r--test/simpleTests.py74
10 files changed, 290 insertions, 4 deletions
diff --git a/doc/ScintillaDoc.html b/doc/ScintillaDoc.html
index 9cc5cae18..689d9c949 100644
--- a/doc/ScintillaDoc.html
+++ b/doc/ScintillaDoc.html
@@ -3380,6 +3380,10 @@ struct Sci_TextToFind {
syntax. Tabs are normally used in editors to insert a tab character or to pad text with spaces
up to the next tab.</p>
+ <p>When Scintilla is laying out a section of text, text after a tab character will usually be
+ displayed at the next multiple of TABWIDTH columns from the left. However, it is also possible
+ to explicitly set tabstops in pixels for each line.</p>
+
<p>Scintilla can be set to treat tab and backspace in the white space at the start of a line in
a special way: inserting a tab indents the line to the next indent position rather than just
inserting a tab at the current character position and backspace unindents the line rather than
@@ -3387,6 +3391,9 @@ struct Sci_TextToFind {
you to generate code.</p>
<code><a class="message" href="#SCI_SETTABWIDTH">SCI_SETTABWIDTH(int widthInChars)</a><br />
<a class="message" href="#SCI_GETTABWIDTH">SCI_GETTABWIDTH</a><br />
+ <a class="message" href="#SCI_CLEARTABSTOPS">SCI_CLEARTABSTOPS(int line)</a><br />
+ <a class="message" href="#SCI_ADDTABSTOP">SCI_ADDTABSTOP(int line, int x)</a><br />
+ <a class="message" href="#SCI_GETNEXTTABSTOP">SCI_GETNEXTTABSTOP(int line, int x)</a><br />
<a class="message" href="#SCI_SETUSETABS">SCI_SETUSETABS(bool useTabs)</a><br />
<a class="message" href="#SCI_GETUSETABS">SCI_GETUSETABS</a><br />
<a class="message" href="#SCI_SETINDENT">SCI_SETINDENT(int widthInChars)</a><br />
@@ -3413,6 +3420,14 @@ struct Sci_TextToFind {
character in <code>STYLE_DEFAULT</code>. The default tab width is 8 characters. There are no
limits on tab sizes, but values less than 1 or large values may have undesirable effects.</p>
+ <p><b id="SCI_CLEARTABSTOPS">SCI_CLEARTABSTOPS(int line)</b><br />
+ <b id="SCI_ADDTABSTOP">SCI_ADDTABSTOP(int line, int x)</b><br />
+ <b id="SCI_GETNEXTTABSTOP">SCI_GETNEXTTABSTOP(int line, int x)</b><br />
+ <code>SCI_CLEARTABSTOPS</code> clears explicit tabstops on a line. <code>SCI_ADDTABSTOP</code>
+ adds an explicit tabstop at the specified distance from the left (in pixels), and
+ <code>SCI_GETNEXTTABSTOP</code> gets the next explicit tabstop position, or zero if there
+ aren't any.</p>
+
<p><b id="SCI_SETUSETABS">SCI_SETUSETABS(bool useTabs)</b><br />
<b id="SCI_GETUSETABS">SCI_GETUSETABS</b><br />
<code>SCI_SETUSETABS</code> determines whether indentation should be created out of a mixture
diff --git a/doc/ScintillaHistory.html b/doc/ScintillaHistory.html
index 6cefeab13..3bb58f509 100644
--- a/doc/ScintillaHistory.html
+++ b/doc/ScintillaHistory.html
@@ -452,6 +452,7 @@
<td>johnsonj</td>
</tr><tr>
<td>Vicente</td>
+ <td>Nick Gravgaard</td>
</tr>
</table>
<p>
@@ -483,6 +484,9 @@
<a href="http://sourceforge.net/p/scintilla/bugs/1532/">Bug #1532</a>.
</li>
<li>
+ Explicit tab stops may be set for each line.
+ </li>
+ <li>
On Windows, when using Korean input methods, IME composition is moved from a
separate window into the Scintilla window.
</li>
diff --git a/include/Scintilla.h b/include/Scintilla.h
index deb945c6a..949d42267 100644
--- a/include/Scintilla.h
+++ b/include/Scintilla.h
@@ -92,6 +92,9 @@ typedef sptr_t (*SciFnDirect)(sptr_t ptr, unsigned int iMessage, uptr_t wParam,
#define SCI_SETBUFFEREDDRAW 2035
#define SCI_SETTABWIDTH 2036
#define SCI_GETTABWIDTH 2121
+#define SCI_CLEARTABSTOPS 2675
+#define SCI_ADDTABSTOP 2676
+#define SCI_GETNEXTTABSTOP 2677
#define SC_CP_UTF8 65001
#define SCI_SETCODEPAGE 2037
#define MARKER_MAX 31
@@ -957,7 +960,8 @@ typedef sptr_t (*SciFnDirect)(sptr_t ptr, unsigned int iMessage, uptr_t wParam,
#define SC_MOD_CONTAINER 0x40000
#define SC_MOD_LEXERSTATE 0x80000
#define SC_MOD_INSERTCHECK 0x100000
-#define SC_MODEVENTMASKALL 0x1FFFFF
+#define SC_MOD_CHANGETABSTOPS 0x200000
+#define SC_MODEVENTMASKALL 0x3FFFFF
#define SC_UPDATE_CONTENT 0x1
#define SC_UPDATE_SELECTION 0x2
#define SC_UPDATE_V_SCROLL 0x4
diff --git a/include/Scintilla.iface b/include/Scintilla.iface
index cfbc7bc9f..b0399e5ee 100644
--- a/include/Scintilla.iface
+++ b/include/Scintilla.iface
@@ -226,6 +226,15 @@ set void SetTabWidth=2036(int tabWidth,)
# Retrieve the visible size of a tab.
get int GetTabWidth=2121(,)
+# Clear explicit tabstops on a line.
+fun void ClearTabStops=2675(int line,)
+
+# Add an explicit tab stop for a line.
+fun void AddTabStop=2676(int line, int x)
+
+# Find the next explicit tab stop position on a line after a position.
+fun int GetNextTabStop=2677(int line, int x)
+
# The SC_CP_UTF8 value can be used to enter Unicode mode.
# This is the same value as CP_UTF8 in Windows
val SC_CP_UTF8=65001
@@ -2523,7 +2532,8 @@ val SC_MOD_CHANGEANNOTATION=0x20000
val SC_MOD_CONTAINER=0x40000
val SC_MOD_LEXERSTATE=0x80000
val SC_MOD_INSERTCHECK=0x100000
-val SC_MODEVENTMASKALL=0x1FFFFF
+val SC_MOD_CHANGETABSTOPS=0x200000
+val SC_MODEVENTMASKALL=0x3FFFFF
enu Update=SC_UPDATE_
val SC_UPDATE_CONTENT=0x1
diff --git a/src/EditView.cxx b/src/EditView.cxx
index b033c7cf6..c46a0fbcf 100644
--- a/src/EditView.cxx
+++ b/src/EditView.cxx
@@ -29,6 +29,7 @@
#include "RunStyles.h"
#include "ContractionState.h"
#include "CellBuffer.h"
+#include "PerLine.h"
#include "KeyMap.h"
#include "Indicator.h"
#include "XPM.h"
@@ -170,6 +171,7 @@ void DrawStyledText(Surface *surface, const ViewStyle &vs, int styleOffset, PRec
const XYPOSITION epsilon = 0.0001f; // A small nudge to avoid floating point precision issues
EditView::EditView() {
+ ldTabstops = NULL;
hideSelection = false;
drawOverstrikeCaret = true;
bufferedDraw = true;
@@ -185,6 +187,11 @@ EditView::EditView() {
posCache.SetSize(0x400);
}
+EditView::~EditView() {
+ delete ldTabstops;
+ ldTabstops = NULL;
+}
+
bool EditView::SetTwoPhaseDraw(bool twoPhaseDraw) {
const PhasesDraw phasesDrawNew = twoPhaseDraw ? phasesTwo : phasesOne;
const bool redraw = phasesDraw != phasesDrawNew;
@@ -203,6 +210,54 @@ bool EditView::LinesOverlap() const {
return phasesDraw == phasesMultiple;
}
+void EditView::ClearAllTabstops() {
+ delete ldTabstops;
+ ldTabstops = 0;
+}
+
+int EditView::NextTabstopPos(int line, int x, int tabWidth) const {
+ int next = GetNextTabstop(line, x);
+ if (next > 0)
+ return next;
+ return ((((x + 2) / tabWidth) + 1) * tabWidth);
+}
+
+bool EditView::ClearTabstops(int line) {
+ LineTabstops *lt = static_cast<LineTabstops *>(ldTabstops);
+ return lt && lt->ClearTabstops(line);
+}
+
+bool EditView::AddTabstop(int line, int x) {
+ if (!ldTabstops) {
+ ldTabstops = new LineTabstops();
+ }
+ LineTabstops *lt = static_cast<LineTabstops *>(ldTabstops);
+ return lt && lt->AddTabstop(line, x);
+}
+
+int EditView::GetNextTabstop(int line, int x) const {
+ LineTabstops *lt = static_cast<LineTabstops *>(ldTabstops);
+ if (lt) {
+ return lt->GetNextTabstop(line, x);
+ } else {
+ return 0;
+ }
+}
+
+void EditView::LinesAddedOrRemoved(int lineOfPos, int linesAdded) {
+ if (ldTabstops) {
+ if (linesAdded > 0) {
+ for (int line = lineOfPos; line < lineOfPos + linesAdded; line++) {
+ ldTabstops->InsertLine(line);
+ }
+ } else {
+ for (int line = (lineOfPos + -linesAdded) - 1; line >= lineOfPos; line--) {
+ ldTabstops->RemoveLine(line);
+ }
+ }
+ }
+}
+
void EditView::DropGraphics(bool freeObjects) {
if (freeObjects) {
delete pixmapLine;
@@ -397,8 +452,9 @@ void EditView::LayoutLine(const EditModel &model, int line, Surface *surface, co
XYPOSITION representationWidth = vstyle.controlCharWidth;
if (ll->chars[ts.start] == '\t') {
// Tab is a special case of representation, taking a variable amount of space
- representationWidth =
- ((static_cast<int>((ll->positions[ts.start] + 2) / vstyle.tabWidth) + 1) * vstyle.tabWidth) - ll->positions[ts.start];
+ const int x = static_cast<int>(ll->positions[ts.start]);
+ const int tabWidth = static_cast<int>(vstyle.tabWidth);
+ representationWidth = static_cast<XYPOSITION>(NextTabstopPos(line, x, tabWidth) - ll->positions[ts.start]);
} else {
if (representationWidth <= 0.0) {
XYPOSITION positionsRepr[256]; // Should expand when needed
diff --git a/src/EditView.h b/src/EditView.h
index 7d64aff90..18451104f 100644
--- a/src/EditView.h
+++ b/src/EditView.h
@@ -48,6 +48,7 @@ void DrawStyledText(Surface *surface, const ViewStyle &vs, int styleOffset, PRec
class EditView {
public:
PrintParameters printParameters;
+ PerLine *ldTabstops;
bool hideSelection;
bool drawOverstrikeCaret;
@@ -78,11 +79,19 @@ public:
PositionCache posCache;
EditView();
+ virtual ~EditView();
bool SetTwoPhaseDraw(bool twoPhaseDraw);
bool SetPhasesDraw(int phases);
bool LinesOverlap() const;
+ void ClearAllTabstops();
+ int NextTabstopPos(int line, int x, int tabWidth) const;
+ bool ClearTabstops(int line);
+ bool AddTabstop(int line, int x);
+ int GetNextTabstop(int line, int x) const;
+ void LinesAddedOrRemoved(int lineOfPos, int linesAdded);
+
void DropGraphics(bool freeObjects);
void AllocateGraphics(const ViewStyle &vsDraw);
void RefreshPixMaps(Surface *surfaceWindow, WindowID wid, const ViewStyle &vsDraw);
diff --git a/src/Editor.cxx b/src/Editor.cxx
index 7fb849114..1c1e38d65 100644
--- a/src/Editor.cxx
+++ b/src/Editor.cxx
@@ -29,6 +29,7 @@
#include "RunStyles.h"
#include "ContractionState.h"
#include "CellBuffer.h"
+#include "PerLine.h"
#include "KeyMap.h"
#include "Indicator.h"
#include "XPM.h"
@@ -1987,6 +1988,9 @@ void Editor::ClearAll() {
pdoc->MarginClearAll();
}
}
+
+ view.ClearAllTabstops();
+
sel.Clear();
SetTopLine(0);
SetVerticalScrollPos();
@@ -2457,6 +2461,9 @@ void Editor::NotifyModified(Document *, DocModification mh, void *) {
Redraw();
}
}
+ if (mh.modificationType & SC_MOD_CHANGETABSTOPS) {
+ Redraw();
+ }
if (mh.modificationType & SC_MOD_LEXERSTATE) {
if (paintState == painting) {
CheckForChangeOutsidePaint(
@@ -2520,6 +2527,7 @@ void Editor::NotifyModified(Document *, DocModification mh, void *) {
} else {
cs.DeleteLines(lineOfPos, -mh.linesAdded);
}
+ view.LinesAddedOrRemoved(lineOfPos, mh.linesAdded);
}
if (mh.modificationType & SC_MOD_CHANGEANNOTATION) {
int lineDoc = pdoc->LineFromPosition(mh.position);
@@ -4813,6 +4821,8 @@ void Editor::SetDocPointer(Document *document) {
view.llc.Deallocate();
NeedWrapping();
+ view.ClearAllTabstops();
+
pdoc->AddWatcher(this, 0);
SetScrollBars();
Redraw();
@@ -5983,6 +5993,23 @@ sptr_t Editor::WndProc(unsigned int iMessage, uptr_t wParam, sptr_t lParam) {
case SCI_GETTABWIDTH:
return pdoc->tabInChars;
+ case SCI_CLEARTABSTOPS:
+ if (view.ClearTabstops(static_cast<int>(wParam))) {
+ DocModification mh(SC_MOD_CHANGETABSTOPS, 0, 0, 0, 0, static_cast<int>(wParam));
+ NotifyModified(pdoc, mh, NULL);
+ }
+ break;
+
+ case SCI_ADDTABSTOP:
+ if (view.AddTabstop(static_cast<int>(wParam), static_cast<int>(lParam))) {
+ DocModification mh(SC_MOD_CHANGETABSTOPS, 0, 0, 0, 0, static_cast<int>(wParam));
+ NotifyModified(pdoc, mh, NULL);
+ }
+ break;
+
+ case SCI_GETNEXTTABSTOP:
+ return view.GetNextTabstop(static_cast<int>(wParam), static_cast<int>(lParam));
+
case SCI_SETINDENT:
pdoc->indentInChars = static_cast<int>(wParam);
if (pdoc->indentInChars != 0)
diff --git a/src/PerLine.cxx b/src/PerLine.cxx
index 8b0dbc44b..8fd96cbed 100644
--- a/src/PerLine.cxx
+++ b/src/PerLine.cxx
@@ -7,6 +7,7 @@
#include <string.h>
+#include <vector>
#include <algorithm>
#include "Platform.h"
@@ -484,3 +485,72 @@ int LineAnnotation::Lines(int line) const {
else
return 0;
}
+
+LineTabstops::~LineTabstops() {
+ Init();
+}
+
+void LineTabstops::Init() {
+ for (int line = 0; line < tabstops.Length(); line++) {
+ delete tabstops[line];
+ }
+ tabstops.DeleteAll();
+}
+
+void LineTabstops::InsertLine(int line) {
+ if (tabstops.Length()) {
+ tabstops.EnsureLength(line);
+ tabstops.Insert(line, 0);
+ }
+}
+
+void LineTabstops::RemoveLine(int line) {
+ if (tabstops.Length() > line) {
+ delete tabstops[line];
+ tabstops.Delete(line);
+ }
+}
+
+bool LineTabstops::ClearTabstops(int line) {
+ if (line < tabstops.Length()) {
+ TabstopList *tl = tabstops[line];
+ if (tl) {
+ tl->clear();
+ return true;
+ }
+ }
+ return false;
+}
+
+bool LineTabstops::AddTabstop(int line, int x) {
+ tabstops.EnsureLength(line + 1);
+ if (!tabstops[line]) {
+ tabstops[line] = new TabstopList();
+ }
+
+ TabstopList *tl = tabstops[line];
+ if (tl) {
+ // tabstop positions are kept in order - insert in the right place
+ std::vector<int>::iterator it = std::lower_bound(tl->begin(), tl->end(), x);
+ // don't insert duplicates
+ if (it == tl->end() || *it != x) {
+ tl->insert(it, x);
+ return true;
+ }
+ }
+ return false;
+}
+
+int LineTabstops::GetNextTabstop(int line, int x) const {
+ if (line < tabstops.Length()) {
+ TabstopList *tl = tabstops[line];
+ if (tl) {
+ for (size_t i = 0; i < tl->size(); i++) {
+ if ((*tl)[i] > x) {
+ return (*tl)[i];
+ }
+ }
+ }
+ }
+ return 0;
+}
diff --git a/src/PerLine.h b/src/PerLine.h
index 70d0023e4..4bf1c88fd 100644
--- a/src/PerLine.h
+++ b/src/PerLine.h
@@ -112,6 +112,23 @@ public:
int Lines(int line) const;
};
+typedef std::vector<int> TabstopList;
+
+class LineTabstops : public PerLine {
+ SplitVector<TabstopList *> tabstops;
+public:
+ LineTabstops() {
+ }
+ virtual ~LineTabstops();
+ virtual void Init();
+ virtual void InsertLine(int line);
+ virtual void RemoveLine(int line);
+
+ bool ClearTabstops(int line);
+ bool AddTabstop(int line, int x);
+ int GetNextTabstop(int line, int x) const;
+};
+
#ifdef SCI_NAMESPACE
}
#endif
diff --git a/test/simpleTests.py b/test/simpleTests.py
index 67299e661..590d288b8 100644
--- a/test/simpleTests.py
+++ b/test/simpleTests.py
@@ -2042,6 +2042,80 @@ class TestWordChars(unittest.TestCase):
data = self.ed.GetPunctuationChars(None)
self.assertCharSetsEqual(data, expected)
+class TestExplicitTabStops(unittest.TestCase):
+
+ def setUp(self):
+ self.xite = Xite.xiteFrame
+ self.ed = self.xite.ed
+ self.ed.ClearAll()
+ self.ed.EmptyUndoBuffer()
+ # 2 lines of 4 characters
+ self.t = b"fun(\nint)"
+ self.ed.AddText(len(self.t), self.t)
+
+ def testAddingAndClearing(self):
+ self.assertEquals(self.ed.GetNextTabStop(0,0), 0)
+
+ # Add a tab stop at 7
+ self.ed.AddTabStop(0, 7)
+ # Check added
+ self.assertEquals(self.ed.GetNextTabStop(0,0), 7)
+ # Check does not affect line 1
+ self.assertEquals(self.ed.GetNextTabStop(1,0), 0)
+
+ # Add a tab stop at 18
+ self.ed.AddTabStop(0, 18)
+ # Check added
+ self.assertEquals(self.ed.GetNextTabStop(0,0), 7)
+ self.assertEquals(self.ed.GetNextTabStop(0,7), 18)
+ # Check does not affect line 1
+ self.assertEquals(self.ed.GetNextTabStop(1,0), 0)
+ self.assertEquals(self.ed.GetNextTabStop(1,7), 0)
+
+ # Add a tab stop between others at 13
+ self.ed.AddTabStop(0, 13)
+ # Check added
+ self.assertEquals(self.ed.GetNextTabStop(0,0), 7)
+ self.assertEquals(self.ed.GetNextTabStop(0,7), 13)
+ self.assertEquals(self.ed.GetNextTabStop(0,13), 18)
+ # Check does not affect line 1
+ self.assertEquals(self.ed.GetNextTabStop(1,0), 0)
+ self.assertEquals(self.ed.GetNextTabStop(1,7), 0)
+
+ self.ed.ClearTabStops(0)
+ # Check back to original state
+ self.assertEquals(self.ed.GetNextTabStop(0,0), 0)
+
+ def testLineInsertionDeletion(self):
+ # Add a tab stop at 7 on line 1
+ self.ed.AddTabStop(1, 7)
+ # Check added
+ self.assertEquals(self.ed.GetNextTabStop(1,0), 7)
+
+ # More text at end
+ self.ed.AddText(len(self.t), self.t)
+ self.assertEquals(self.ed.GetNextTabStop(0,0), 0)
+ self.assertEquals(self.ed.GetNextTabStop(1,0), 7)
+ self.assertEquals(self.ed.GetNextTabStop(2,0), 0)
+ self.assertEquals(self.ed.GetNextTabStop(3,0), 0)
+
+ # Another 2 lines before explicit line moves the explicit tab stop
+ data = b"x\ny\n"
+ self.ed.InsertText(4, data)
+ self.assertEquals(self.ed.GetNextTabStop(0,0), 0)
+ self.assertEquals(self.ed.GetNextTabStop(1,0), 0)
+ self.assertEquals(self.ed.GetNextTabStop(2,0), 0)
+ self.assertEquals(self.ed.GetNextTabStop(3,0), 7)
+ self.assertEquals(self.ed.GetNextTabStop(4,0), 0)
+ self.assertEquals(self.ed.GetNextTabStop(5,0), 0)
+
+ # Undo moves the explicit tab stop back
+ self.ed.Undo()
+ self.assertEquals(self.ed.GetNextTabStop(0,0), 0)
+ self.assertEquals(self.ed.GetNextTabStop(1,0), 7)
+ self.assertEquals(self.ed.GetNextTabStop(2,0), 0)
+ self.assertEquals(self.ed.GetNextTabStop(3,0), 0)
+
if __name__ == '__main__':
uu = Xite.main("simpleTests")
#~ for x in sorted(uu.keys()):