diff options
author | Neil <nyamatongwe@gmail.com> | 2014-08-08 10:01:55 +1000 |
---|---|---|
committer | Neil <nyamatongwe@gmail.com> | 2014-08-08 10:01:55 +1000 |
commit | c5905297575b23f9454cb0dfe574140c2a329c95 (patch) | |
tree | 5d1c711c675c9bfddf13a741149e2220c26633fe | |
parent | ced6baba095c3ee140c1527080ff68faecab31ca (diff) | |
download | scintilla-mirror-c5905297575b23f9454cb0dfe574140c2a329c95.tar.gz |
Implement explicit tab stops per line.
From Nick Gravgaard.
-rw-r--r-- | doc/ScintillaDoc.html | 15 | ||||
-rw-r--r-- | doc/ScintillaHistory.html | 4 | ||||
-rw-r--r-- | include/Scintilla.h | 6 | ||||
-rw-r--r-- | include/Scintilla.iface | 12 | ||||
-rw-r--r-- | src/EditView.cxx | 60 | ||||
-rw-r--r-- | src/EditView.h | 9 | ||||
-rw-r--r-- | src/Editor.cxx | 27 | ||||
-rw-r--r-- | src/PerLine.cxx | 70 | ||||
-rw-r--r-- | src/PerLine.h | 17 | ||||
-rw-r--r-- | test/simpleTests.py | 74 |
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()): |