aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authormitchell <unknown>2018-03-10 10:58:26 -0500
committermitchell <unknown>2018-03-10 10:58:26 -0500
commitc0373e036e965a70045971e2abc582cb4bf12a4e (patch)
tree24bbd02ff5a9c032157a24a4856c325398455bd4
parentb086b4d30fee49c09184b2145e3a222925ac3fc8 (diff)
downloadscintilla-mirror-c0373e036e965a70045971e2abc582cb4bf12a4e.tar.gz
Added curses platform.
-rw-r--r--README4
-rw-r--r--curses/Makefile38
-rw-r--r--curses/README.md78
-rw-r--r--curses/ScintillaCurses.cxx1462
-rw-r--r--curses/ScintillaCurses.h131
-rw-r--r--curses/THANKS.md14
-rw-r--r--curses/jinx/Makefile19
-rw-r--r--curses/jinx/jinx.c106
-rw-r--r--doc/ScintillaRelated.html4
-rw-r--r--scripts/HeaderCheck.py1
-rw-r--r--scripts/HeaderOrder.txt4
11 files changed, 1857 insertions, 4 deletions
diff --git a/README b/README
index 61771676b..d1ce4db18 100644
--- a/README
+++ b/README
@@ -83,3 +83,7 @@ To build Scintilla, run xcodebuild in the scintilla/cocoa/ScintillaFramework dir
*** Qt version ***
See the qt/README file to build Scintilla with Qt.
+
+*** Curses version ***
+
+See the curses/README.md file to build Scintilla with curses.
diff --git a/curses/Makefile b/curses/Makefile
new file mode 100644
index 000000000..42a8e4e0c
--- /dev/null
+++ b/curses/Makefile
@@ -0,0 +1,38 @@
+# Copyright 2012-2018 Mitchell mitchell.att.foicica.com. See License.txt.
+
+.SUFFIXES: .cxx .c .o .h .a
+
+AR = ar
+CXX = g++
+INCLUDEDIRS = -I ../include -I ../src -I ../lexlib
+CXXFLAGS = -std=c++11 -pedantic -DCURSES -DSCI_LEXER $(INCLUDEDIRS) -Wall \
+ -Wextra -Wno-missing-field-initializers
+ifdef DEBUG
+ CXXFLAGS += -DDEBUG -g
+else
+ CXXFLAGS += -DNDEBUG -Os
+endif
+CURSES_FLAGS =
+
+scintilla = ../bin/scintilla.a
+lexers = $(addsuffix .o,$(basename $(sort $(notdir $(wildcard ../lexers/Lex*.cxx)))))
+
+vpath %.h ../src ../include ../lexlib
+vpath %.cxx ../src ../lexlib ../lexers
+
+all: $(scintilla)
+.cxx.o:
+ $(CXX) $(CXXFLAGS) $(CURSES_FLAGS) -c $<
+$(scintilla): AutoComplete.o CallTip.o CaseConvert.o CaseFolder.o Catalogue.o \
+ CellBuffer.o CharacterCategory.o CharClassify.o \
+ ContractionState.o Decoration.o Document.o EditModel.o Editor.o \
+ EditView.o ExternalLexer.o Indicator.o KeyMap.o LineMarker.o \
+ MarginView.o PerLine.o PositionCache.o RESearch.o RunStyles.o \
+ ScintillaBase.o Selection.o Style.o UniConversion.o ViewStyle.o \
+ XPM.o Accessor.o CharacterSet.o LexerBase.o LexerModule.o \
+ LexerNoExceptions.o LexerSimple.o PropSetSimple.o StyleContext.o \
+ WordList.o $(lexers) ScintillaCurses.o
+ $(AR) rc $@ $^
+ touch $@
+clean:
+ rm -f *.o $(scintilla)
diff --git a/curses/README.md b/curses/README.md
new file mode 100644
index 000000000..ecbc2a5db
--- /dev/null
+++ b/curses/README.md
@@ -0,0 +1,78 @@
+# Scintilla curses
+
+## Overview
+
+This is an implementation of [Scintilla][] for curses platforms including:
+
+* [ncurses][]
+* [PDCurses][]
+* X/Open Curses
+
+[Scintilla]: http://scintilla.org
+[ncurses]: http://invisible-island.net/ncurses/
+[PDCurses]: http://pdcurses.sourceforge.net/
+
+## Compiling
+
+Simply run `make` to build the usual `../bin/scintilla.a`.
+
+## Usage
+
+It is highly recommended to use Scintilla curses in a UTF-8-aware terminal with
+a font that supports many UTF-8 characters ("DejaVu Sans Mono" is one of them)
+because Scintilla curses makes use of UTF-8 characters when drawing wrap
+symbols, some marker symbols, and call tip arrows.
+
+Note: `setlocale(LC_CTYPE, "")` must be called before initializing curses in
+order to display UTF-8 characters properly in ncursesw.
+
+## Curses Compatibility
+
+Scintilla curses lacks some Scintilla features due to the terminal's
+constraints:
+
+* Any settings with alpha values are not supported.
+* Autocompletion lists cannot show images (pixmap surfaces are not supported).
+ Instead, they show the first character in the string passed to
+ [`SCI_REGISTERIMAGE`][].
+* Buffered and two-phase drawing is not supported.
+* Caret settings like period, line style, and width are not supported
+ (terminals use block carets with their own period definitions).
+* Code pages other than UTF-8 have not been tested and it is possible some
+ curses implementations do not support them.
+* Drag and drop is not supported.
+* Edge lines are not displayed properly (the line is drawn over by text lines).
+* Extra ascent and descent for lines is not supported.
+* Fold lines cannot be drawn above or below lines.
+* Hotspot underlines are not drawn on mouse hover (`surface->FillRectangle()` is
+ not supported).
+* Indicators other than `INDIC_ROUNDBOX` and `INDIC_STRAIGHTBOX` are not drawn
+ (`surface->LineTo()` and `surface->FillRectangle()` are not supported for
+ drawing indicator shapes and pixmap surfaces are not supported). Translucent
+ drawing and rounded corners are not supported either.
+* Some complex marker types are not drawn properly or at all (pixmap surfaces
+ are not supported and `surface->LineTo()` is not supported for drawing some
+ marker shapes).
+* Mouse cursor types are not supported.
+* Up to 16 colors are supported, regardless of how many colors the terminal
+ supports. They are (in "0xBBGGRR" format): black (`0x000000`), red
+ (`0x000080`), green (`0x008000`), yellow (`0x008080`), blue (`0x800000`),
+ magenta (`0x800080`), cyan (`0x808000`), white (`0xC0C0C0`), light black
+ (`0x404040`), light red (`0x0000FF`), light green (`0x00FF00`), light yellow
+ (`0x00FFFF`), light blue (`0xFF0000`), light magenta (`0xFF00FF`), light cyan
+ (`0xFFFF00`), and light white (`0xFFFFFF`). Even if your terminal uses a
+ different color map, you must use these color values with Scintilla;
+ unrecognized colors are set to white by default. For some terminals, you may
+ need to set a lexer style's `bold` attribute in order to use the light color
+ variant.
+* Some styles settings like font name, font size, and italic do not display
+ properly (terminals use one only font, size and variant).
+* X selections (primary and secondary) are not integrated into the clipboard.
+* Zoom is not supported (terminal font size is fixed).
+
+[`SCI_REGISTERIMAGE`]: http://scintilla.org/ScintillaDoc.html#SCI_REGISTERIMAGE
+
+## `jinx`
+
+`jinx` is an example of using Scintilla with curses. You can build it by going
+into `jinx/` and running `make`.
diff --git a/curses/ScintillaCurses.cxx b/curses/ScintillaCurses.cxx
new file mode 100644
index 000000000..3e78f1cca
--- /dev/null
+++ b/curses/ScintillaCurses.cxx
@@ -0,0 +1,1462 @@
+// Copyright 2012-2018 Mitchell mitchell.att.foicica.com. See License.txt.
+// Scintilla implemented in a curses (terminal) environment.
+// Contains platform facilities and a curses-specific subclass of ScintillaBase.
+// Note: setlocale(LC_CTYPE, "") must be called before initializing curses in
+// order to display UTF-8 characters properly in ncursesw.
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <math.h>
+#include <wchar.h>
+
+#include <stdexcept>
+#include <string>
+#include <vector>
+#include <map>
+#include <algorithm>
+#include <memory>
+
+#include "Platform.h"
+
+#include "ILexer.h"
+#include "Scintilla.h"
+#include "Position.h"
+#include "UniqueString.h"
+#include "SplitVector.h"
+#include "Partitioning.h"
+#include "RunStyles.h"
+#include "ContractionState.h"
+#include "CellBuffer.h"
+#include "CallTip.h"
+#include "KeyMap.h"
+#include "Indicator.h"
+#include "XPM.h"
+#include "LineMarker.h"
+#include "Style.h"
+#include "ViewStyle.h"
+#include "CharClassify.h"
+#include "Decoration.h"
+#include "CaseFolder.h"
+#include "Document.h"
+#include "UniConversion.h"
+#include "Selection.h"
+#include "PositionCache.h"
+#include "EditModel.h"
+#include "MarginView.h"
+#include "EditView.h"
+#include "Editor.h"
+#include "AutoComplete.h"
+#include "ScintillaBase.h"
+#include "ScintillaCurses.h"
+
+/**
+ * Returns the given Scintilla `WindowID` as a curses `WINDOW`.
+ * @param w A Scintilla `WindowID`.
+ * @return curses `WINDOW`.
+ */
+#define _WINDOW(w) reinterpret_cast<WINDOW *>(w)
+
+#if _WIN32
+#define wcwidth(_) 1 // TODO: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c
+#endif
+
+// Font handling.
+
+/**
+ * Allocates a new Scintilla font for curses.
+ * Since terminals handle fonts on their own, the only use for Scintilla font
+ * objects is to indicate which attributes terminal characters have. This is
+ * done in `Font::Create()`.
+ * @see Font::Create
+ */
+Font::Font() : fid(0) {}
+/** Deletes the font. Currently empty. */
+Font::~Font() {}
+/**
+ * Sets terminal character attributes for a particular font.
+ * These attributes are a union of curses attributes and stored in the font's
+ * `fid`.
+ * The curses attributes are not constructed from various fields in *fp* since
+ * there is no `underline` parameter. Instead, you need to manually set the
+ * `weight` parameter to be the union of your desired attributes.
+ * Scintillua (http://foicica.com/scintillua) has an example of this.
+ */
+void Font::Create(const FontParameters &fp) {
+ Release();
+ attr_t attrs = 0;
+ if (fp.weight == SC_WEIGHT_BOLD)
+ attrs = A_BOLD;
+ else if (fp.weight != SC_WEIGHT_NORMAL && fp.weight != SC_WEIGHT_SEMIBOLD)
+ attrs = fp.weight; // font attributes are stored in fp.weight
+ fid = reinterpret_cast<FontID>(attrs);
+}
+/** Releases a font's resources. */
+void Font::Release() { fid = 0; }
+
+// Color handling.
+
+static int COLOR_LBLACK = COLOR_BLACK + 8;
+static int COLOR_LRED = COLOR_RED + 8;
+static int COLOR_LGREEN = COLOR_GREEN + 8;
+static int COLOR_LYELLOW = COLOR_YELLOW + 8;
+static int COLOR_LBLUE = COLOR_BLUE + 8;
+static int COLOR_LMAGENTA = COLOR_MAGENTA + 8;
+static int COLOR_LCYAN = COLOR_CYAN + 8;
+static int COLOR_LWHITE = COLOR_WHITE + 8;
+
+static bool initialized_colors = false;
+
+/**
+ * Initializes colors in curses if they have not already been initialized.
+ * Creates all possible color pairs using the `SCI_COLOR_PAIR()` macro.
+ * This is called automatically from `scintilla_new()`.
+ */
+static void init_colors() {
+ if (initialized_colors) return;
+ if (has_colors()) {
+ start_color();
+ for (int back = 0; back < ((COLORS < 16) ? 8 : 16); back++)
+ for (int fore = 0; fore < ((COLORS < 16) ? 8 : 16); fore++)
+ init_pair(SCI_COLOR_PAIR(fore, back), fore, back);
+ if (COLORS < 16) {
+ // Do not distinguish between light and normal colors.
+ COLOR_LBLACK -= 8;
+ COLOR_LRED -= 8;
+ COLOR_LGREEN -= 8;
+ COLOR_LYELLOW -= 8;
+ COLOR_LBLUE -= 8;
+ COLOR_LMAGENTA -= 8;
+ COLOR_LCYAN -= 8;
+ COLOR_LWHITE -= 8;
+ }
+ }
+ initialized_colors = true;
+}
+
+static ColourDesired BLACK(0, 0, 0);
+static ColourDesired RED(0x80, 0, 0);
+static ColourDesired GREEN(0, 0x80, 0);
+static ColourDesired YELLOW(0x80, 0x80, 0);
+static ColourDesired BLUE(0, 0, 0x80);
+static ColourDesired MAGENTA(0x80, 0, 0x80);
+static ColourDesired CYAN(0, 0x80, 0x80);
+static ColourDesired WHITE(0xC0, 0xC0, 0xC0);
+static ColourDesired LBLACK(0x40, 0x40, 0x40);
+static ColourDesired LRED(0xFF, 0, 0);
+static ColourDesired LGREEN(0, 0xFF, 0);
+static ColourDesired LYELLOW(0xFF, 0xFF, 0);
+static ColourDesired LBLUE(0, 0, 0xFF);
+static ColourDesired LMAGENTA(0xFF, 0, 0xFF);
+static ColourDesired LCYAN(0, 0xFF, 0xFF);
+static ColourDesired LWHITE(0xFF, 0xFF, 0xFF);
+static ColourDesired SCI_COLORS[] = {
+ BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE, LBLACK, LRED, LGREEN,
+ LYELLOW, LBLUE, LMAGENTA, LCYAN, LWHITE
+};
+
+/**
+ * Returns a curses color for the given Scintilla color.
+ * Recognized colors are: black (0x000000), red (0x800000), green (0x008000),
+ * yellow (0x808000), blue (0x000080), magenta (0x800080), cyan (0x008080),
+ * white (0xc0c0c0), light black (0x404040), light red (0xff0000), light green
+ * (0x00ff00), light yellow (0xffff00), light blue (0x0000ff), light magenta
+ * (0xff00ff), light cyan (0x00ffff), and light white (0xffffff). If the color
+ * is not recognized, returns `COLOR_WHITE` by default.
+ * @param color Color to get a curses color for.
+ * @return curses color
+ */
+static int term_color(ColourDesired color) {
+ if (color == BLACK) return COLOR_BLACK;
+ else if (color == RED) return COLOR_RED;
+ else if (color == GREEN) return COLOR_GREEN;
+ else if (color == YELLOW) return COLOR_YELLOW;
+ else if (color == BLUE) return COLOR_BLUE;
+ else if (color == MAGENTA) return COLOR_MAGENTA;
+ else if (color == CYAN) return COLOR_CYAN;
+ else if (color == LBLACK) return COLOR_LBLACK;
+ else if (color == LRED) return COLOR_LRED;
+ else if (color == LGREEN) return COLOR_LGREEN;
+ else if (color == LYELLOW) return COLOR_LYELLOW;
+ else if (color == LBLUE) return COLOR_LBLUE;
+ else if (color == LMAGENTA) return COLOR_LMAGENTA;
+ else if (color == LCYAN) return COLOR_LCYAN;
+ else if (color == LWHITE) return COLOR_LWHITE;
+ else return COLOR_WHITE;
+}
+
+/**
+ * Returns a curses color for the given curses color.
+ * This overloaded method only exists for the `term_color_pair()` macro.
+ */
+static int term_color(int color) { return color; }
+
+/**
+ * Returns a curses color pair from the given fore and back colors.
+ * @param f Foreground color, either a Scintilla color or curses color.
+ * @param b Background color, either a Scintilla color or curses color.
+ * @return curses color pair suitable for calling `COLOR_PAIR()` with.
+ */
+#define term_color_pair(f, b) SCI_COLOR_PAIR(term_color(f), term_color(b))
+
+// Surface handling.
+
+/**
+ * Implementation of a Scintilla surface for curses.
+ * The surface is initialized with a curses `WINDOW` for drawing on. Since
+ * curses can only show text, many of Scintilla's pixel-based functions are
+ * not implemented.
+ */
+class SurfaceImpl : public Surface {
+ WINDOW *win;
+ PRectangle clip;
+
+ /**
+ * Returns the number of columns used to display the first UTF-8 character in
+ * `s`, taking into account zero-width combining characters.
+ * @param s The string that contains the first UTF-8 character to display.
+ */
+ int grapheme_width(const char *s) {
+ wchar_t wch;
+ if (mbtowc(&wch, s, MB_CUR_MAX) < 1) return 1;
+ int width = wcwidth(wch);
+ return width >= 0 ? width : 1;
+ }
+public:
+ /** Allocates a new Scintilla surface for curses. */
+ SurfaceImpl() : win(0) {}
+ /** Deletes the surface. */
+ ~SurfaceImpl() { Release(); }
+
+ /**
+ * Initializes/reinitializes the surface with a curses `WINDOW` for drawing
+ * on.
+ * @param wid Curses `WINDOW`.
+ */
+ void Init(WindowID wid) {
+ Release();
+ win = _WINDOW(wid);
+ }
+ /** Identical to `Init()` using the given curses `WINDOW`. */
+ void Init(SurfaceID sid, WindowID wid) { Init(wid); }
+ /** Initializing the surface as a pixmap is not implemented. */
+ void InitPixMap(int width, int height, Surface *surface_, WindowID wid) {}
+
+ /** Releases the surface's resources. */
+ void Release() { win = 0; }
+ /**
+ * Returns `true` since this method is only called for pixmap surfaces and
+ * those surfaces are not implemented.
+ */
+ bool Initialised() { return true; }
+ /**
+ * Setting the surface's foreground color is not implemented because all uses
+ * in Scintilla involve special drawing that is not supported in curses.
+ */
+ void PenColour(ColourDesired fore) {}
+ /** Unused; return value irrelevant. */
+ int LogPixelsY() { return 1; }
+ /** Returns 1 since font height is always 1 in curses. */
+ int DeviceHeightFont(int points) { return 1; }
+ /**
+ * Moving to a particular position is not implemented because all uses in
+ * Scintilla involve subsequent calls to `LineTo()`, which is also
+ * unimplemented.
+ */
+ void MoveTo(int x_, int y_) {}
+ /**
+ * Drawing lines is not implemented because more often than not lines are
+ * being drawn for decoration (e.g. line markers, underlines, indicators,
+ * arrows, etc.).
+ */
+ void LineTo(int x_, int y_) {}
+ /**
+ * Draws the character equivalent of shape outlined by the given polygon's
+ * points.
+ * Scintilla only calls this method for CallTip arrows.
+ * Line markers that Scintilla would normally draw as polygons are handled in
+ * `DrawLineMarker()`.
+ */
+ void Polygon(Point *pts, int npts, ColourDesired fore, ColourDesired back) {
+ wattr_set(win, 0, term_color_pair(back, COLOR_WHITE), NULL); // invert
+ if (pts[0].y < pts[npts - 1].y) // up arrow
+ mvwaddstr(win, pts[0].y, pts[npts - 1].x + 1, "▲");
+ else if (pts[0].y > pts[npts - 1].y) // down arrow
+ mvwaddstr(win, pts[0].y - 2, pts[npts - 1].x + 1, "▼");
+ }
+ /**
+ * Scintilla will never call this method.
+ * Line markers that Scintilla would normally draw as rectangles are handled
+ * in `DrawLineMarker()`.
+ */
+ void RectangleDraw(PRectangle rc, ColourDesired fore, ColourDesired back) {}
+ /**
+ * Clears the given portion of the screen with the given background color.
+ * In some cases, it can be determined that whitespace is being drawn. If so,
+ * draw it appropriately instead of clearing the given portion of the screen.
+ */
+ void FillRectangle(PRectangle rc, ColourDesired back) {
+ wattr_set(win, 0, term_color_pair(COLOR_WHITE, back), NULL);
+ chtype ch = ' ';
+ if (fabs(rc.left - (int)rc.left) > 0.1) {
+ // If rc.left is a fractional value (e.g. 4.5) then whitespace dots are
+ // being drawn. Draw them appropriately.
+ // TODO: set color to vs.whitespaceColours.fore and back.
+ wcolor_set(win, term_color_pair(COLOR_BLACK, COLOR_BLACK), NULL);
+ rc.right = (int)rc.right, ch = ACS_BULLET | A_BOLD;
+ }
+ for (int y = rc.top; y < rc.bottom; y++)
+ for (int x = rc.left; x < rc.right; x++)
+ mvwaddch(win, y, x, ch);
+ }
+ /**
+ * Instead of filling a portion of the screen with a surface pixmap, fills the
+ * the screen portion with black.
+ */
+ void FillRectangle(PRectangle rc, Surface &surfacePattern) {
+ FillRectangle(rc, BLACK);
+ }
+ /**
+ * Scintilla will never call this method.
+ * Line markers that Scintilla would normally draw as rounded rectangles are
+ * handled in `DrawLineMarker()`.
+ */
+ void RoundedRectangle(PRectangle rc, ColourDesired fore,
+ ColourDesired back) {}
+ /**
+ * Drawing alpha rectangles is not fully supported.
+ * Instead, fills the background color of the given rectangle with the fill
+ * color, emulating INDIC_STRAIGHTBOX with no transparency.
+ * This is called by Scintilla to draw INDIC_ROUNDBOX and INDIC_STRAIGHTBOX
+ * indicators, text blobs, and translucent line states and selections.
+ */
+ void AlphaRectangle(PRectangle rc, int cornerSize, ColourDesired fill,
+ int alphaFill, ColourDesired outline, int alphaOutline,
+ int flags) {
+ for (int x = rc.left, y = rc.top - 1; x < rc.right; x++) {
+ attr_t attrs = mvwinch(win, y, x) & A_ATTRIBUTES;
+ short pair = PAIR_NUMBER(attrs), fore = COLOR_WHITE;
+ if (pair > 0) pair_content(pair, &fore, NULL);
+ mvwchgat(win, y, x, 1, attrs, term_color_pair(fore, fill), NULL);
+ }
+ }
+ /** Drawing images is not implemented. */
+ void DrawRGBAImage(PRectangle rc, int width, int height,
+ const unsigned char *pixelsImage) {}
+ /**
+ * Scintilla will never call this method.
+ * Line markers that Scintilla would normally draw as circles are handled in
+ * `DrawLineMarker()`.
+ */
+ void Ellipse(PRectangle rc, ColourDesired fore, ColourDesired back) {}
+ /**
+ * Draw an indentation guide.
+ * Scintilla will only call this method when drawing indentation guides or
+ * during certain drawing operations when double buffering is enabled. Since
+ * the latter is not supported, assume the former.
+ */
+ void Copy(PRectangle rc, Point from, Surface &surfaceSource) {
+ // TODO: handle indent guide highlighting.
+ wattr_set(win, 0, term_color_pair(COLOR_BLACK, COLOR_BLACK), NULL);
+ mvwaddch(win, rc.top, rc.left - 1, '|' | A_BOLD);
+ }
+
+ /**
+ * Draws the given text at the given position on the screen with the given
+ * foreground and background colors.
+ * Takes into account any clipping boundaries previously specified.
+ */
+ void DrawTextNoClip(PRectangle rc, Font &font_, XYPOSITION ybase,
+ const char *s, int len, ColourDesired fore,
+ ColourDesired back) {
+ intptr_t attrs = reinterpret_cast<intptr_t>(font_.GetID());
+ wattr_set(win, static_cast<attr_t>(attrs), term_color_pair(fore, back),
+ NULL);
+ if (rc.left < clip.left) {
+ // Do not overwrite margin text.
+ int clip_chars = static_cast<int>(clip.left - rc.left);
+ int offset = 0;
+ for (int chars = 0; offset < len; offset++) {
+ if (!UTF8IsTrailByte((unsigned char)s[offset]))
+ chars += grapheme_width(s + offset);
+ if (chars > clip_chars) break;
+ }
+ s += offset, len -= offset, rc.left = clip.left;
+ }
+ // Do not write beyond right window boundary.
+ int clip_chars = getmaxx(win) - rc.left;
+ int bytes = 0;
+ for (int chars = 0; bytes < len; bytes++) {
+ if (!UTF8IsTrailByte((unsigned char)s[bytes]))
+ chars += grapheme_width(s + bytes);
+ if (chars > clip_chars) break;
+ }
+ mvwaddnstr(win, rc.top, rc.left, s, std::min(len, bytes));
+ }
+ /**
+ * Similar to `DrawTextNoClip()`.
+ * Scintilla calls this method for drawing the caret, text blobs, and
+ * `SC_MARKCHARACTER` line markers.
+ * When drawing control characters, *rc* needs to have its pixel padding
+ * removed since curses has smaller resolution. Similarly when drawing line
+ * markers, *rc* needs to be reshaped.
+ * @see DrawTextNoClip
+ */
+ void DrawTextClipped(PRectangle rc, Font &font_, XYPOSITION ybase,
+ const char *s, int len, ColourDesired fore,
+ ColourDesired back) {
+ if (rc.left >= rc.right) // when drawing text blobs
+ rc.left -= 2, rc.right -= 2, rc.top -= 1, rc.bottom -= 1;
+ DrawTextNoClip(rc, font_, ybase, s, len, fore, back);
+ }
+ /**
+ * Similar to `DrawTextNoClip()`.
+ * Scintilla calls this method for drawing CallTip text and two-phase buffer
+ * text. However, the latter is not supported.
+ */
+ void DrawTextTransparent(PRectangle rc, Font &font_, XYPOSITION ybase,
+ const char *s, int len, ColourDesired fore) {
+ if ((int)rc.top >= getmaxy(win) - 1) return;
+ attr_t attrs = mvwinch(win, (int)rc.top, (int)rc.left);
+ short pair = PAIR_NUMBER(attrs), back = COLOR_BLACK;
+ if (pair > 0) pair_content(pair, NULL, &back);
+ DrawTextNoClip(rc, font_, ybase, s, len, fore, SCI_COLORS[back]);
+ }
+ /**
+ * Measures the width of characters in the given string and writes them to the
+ * given position list.
+ * Curses characters always have a width of 1 if they are not UTF-8 trailing
+ * bytes.
+ */
+ void MeasureWidths(Font &font_, const char *s, int len,
+ XYPOSITION *positions) {
+ for (int i = 0, j = 0; i < len; i++) {
+ if (!UTF8IsTrailByte((unsigned char)s[i])) j += grapheme_width(s + i);
+ positions[i] = j;
+ }
+ }
+ /**
+ * Returns the number of UTF-8 characters in the given string since curses
+ * characters always have a width of 1.
+ */
+ XYPOSITION WidthText(Font &font_, const char *s, int len) {
+ int width = 0;
+ for (int i = 0; i < len; i++)
+ if (!UTF8IsTrailByte((unsigned char)s[i])) width += grapheme_width(s + i);
+ return width;
+ }
+ /** Returns 1 since curses characters always have a width of 1. */
+ XYPOSITION WidthChar(Font &font_, char ch) { return 1; }
+ /** Returns 0 since curses characters have no ascent. */
+ XYPOSITION Ascent(Font &font_) { return 0; }
+ /** Returns 0 since curses characters have no descent. */
+ XYPOSITION Descent(Font &font_) { return 0; }
+ /** Returns 0 since curses characters have no leading. */
+ XYPOSITION InternalLeading(Font &font_) { return 0; }
+ /** Returns 0 since curses characters have no leading. */
+ XYPOSITION ExternalLeading(Font &font_) { return 0; }
+ /** Returns 1 since curses characters always have a height of 1. */
+ XYPOSITION Height(Font &font_) { return 1; }
+ /** Returns 1 since curses characters always have a width of 1. */
+ XYPOSITION AverageCharWidth(Font &font_) { return 1; }
+
+ /**
+ * Ensure text to be drawn in subsequent calls to `DrawText*()` is drawn
+ * within the given rectangle.
+ * This is needed in order to prevent long lines from overwriting margin text
+ * when scrolling to the right.
+ */
+ void SetClip(PRectangle rc) {
+ clip.left = rc.left, clip.top = rc.top;
+ clip.right = rc.right, clip.bottom = rc.bottom;
+ }
+ /** Flushing cache is not implemented. */
+ void FlushCachedState() {}
+
+ /** Unsetting unicode mode is not implemented. UTF-8 is assumed. */
+ void SetUnicodeMode(bool unicodeMode_) {}
+ /** Setting DBCS mode is not implemented. UTF-8 is used. */
+ void SetDBCSMode(int codePage) {}
+
+ /** Draws the text representation of a line marker, if possible. */
+ void DrawLineMarker(PRectangle &rcWhole, Font &fontForCharacter, int tFold,
+ const void *data) {
+ // TODO: handle fold marker highlighting.
+ const LineMarker *marker = reinterpret_cast<const LineMarker *>(data);
+ wattr_set(win, 0, term_color_pair(marker->fore, marker->back), NULL);
+ switch (marker->markType) {
+ case SC_MARK_CIRCLE:
+ mvwaddstr(win, rcWhole.top, rcWhole.left, "●");
+ return;
+ case SC_MARK_SMALLRECT:
+ case SC_MARK_ROUNDRECT:
+ mvwaddstr(win, rcWhole.top, rcWhole.left, "■");
+ return;
+ case SC_MARK_ARROW:
+ mvwaddstr(win, rcWhole.top, rcWhole.left, "►");
+ return;
+ case SC_MARK_SHORTARROW:
+ mvwaddstr(win, rcWhole.top, rcWhole.left, "→");
+ return;
+ case SC_MARK_ARROWDOWN:
+ mvwaddstr(win, rcWhole.top, rcWhole.left, "▼");
+ return;
+ case SC_MARK_MINUS:
+ mvwaddch(win, rcWhole.top, rcWhole.left, '-');
+ return;
+ case SC_MARK_BOXMINUS:
+ case SC_MARK_BOXMINUSCONNECTED:
+ mvwaddstr(win, rcWhole.top, rcWhole.left, "⊟");
+ return;
+ case SC_MARK_CIRCLEMINUS:
+ case SC_MARK_CIRCLEMINUSCONNECTED:
+ mvwaddstr(win, rcWhole.top, rcWhole.left, "⊖");
+ return;
+ case SC_MARK_PLUS:
+ mvwaddch(win, rcWhole.top, rcWhole.left, '+');
+ return;
+ case SC_MARK_BOXPLUS:
+ case SC_MARK_BOXPLUSCONNECTED:
+ mvwaddstr(win, rcWhole.top, rcWhole.left, "⊞");
+ return;
+ case SC_MARK_CIRCLEPLUS:
+ case SC_MARK_CIRCLEPLUSCONNECTED:
+ mvwaddstr(win, rcWhole.top, rcWhole.left, "⊕");
+ return;
+ case SC_MARK_VLINE:
+ mvwaddch(win, rcWhole.top, rcWhole.left, ACS_VLINE);
+ return;
+ case SC_MARK_LCORNER:
+ case SC_MARK_LCORNERCURVE:
+ mvwaddch(win, rcWhole.top, rcWhole.left, ACS_LLCORNER);
+ return;
+ case SC_MARK_TCORNER:
+ case SC_MARK_TCORNERCURVE:
+ mvwaddch(win, rcWhole.top, rcWhole.left, ACS_LTEE);
+ return;
+ case SC_MARK_DOTDOTDOT:
+ mvwaddstr(win, rcWhole.top, rcWhole.left, "…");
+ return;
+ case SC_MARK_ARROWS:
+ mvwaddstr(win, rcWhole.top, rcWhole.left, "»");
+ return;
+ case SC_MARK_FULLRECT:
+ FillRectangle(rcWhole, marker->back);
+ return;
+ case SC_MARK_LEFTRECT:
+ mvwaddstr(win, rcWhole.top, rcWhole.left, "▌");
+ return;
+ case SC_MARK_BOOKMARK:
+ mvwaddstr(win, rcWhole.top, rcWhole.left, "Σ");
+ return;
+ }
+ if (marker->markType >= SC_MARK_CHARACTER) {
+ char ch = static_cast<char>(marker->markType - SC_MARK_CHARACTER);
+ DrawTextClipped(rcWhole, fontForCharacter, rcWhole.bottom, &ch, 1,
+ marker->fore, marker->back);
+ return;
+ }
+ }
+ /** Draws the text representation of a wrap marker. */
+ void DrawWrapMarker(PRectangle rcPlace, bool isEndMarker,
+ ColourDesired wrapColour) {
+ wattr_set(win, 0, term_color_pair(wrapColour, COLOR_BLACK), NULL);
+ mvwaddstr(win, rcPlace.top, rcPlace.left, isEndMarker ? "↩" : "↪");
+ }
+ /** Draws the text representation of a tab arrow. */
+ void DrawTabArrow(PRectangle rcTab) {
+ // TODO: set color to vs.whitespaceColours.fore and back.
+ wattr_set(win, 0, term_color_pair(COLOR_BLACK, COLOR_BLACK), NULL);
+ for (int i = rcTab.left - 1; i < rcTab.right; i++)
+ mvwaddch(win, rcTab.top, i, '-' | A_BOLD);
+ mvwaddch(win, rcTab.top, rcTab.right, '>' | A_BOLD);
+ }
+};
+
+/** Creates a new curses surface. */
+Surface *Surface::Allocate(int) { return new SurfaceImpl(); }
+
+/** Custom function for drawing line markers in curses. */
+static void DrawLineMarker(Surface *surface, PRectangle &rcWhole,
+ Font &fontForCharacter, int tFold, int marginStyle,
+ const void *data) {
+ reinterpret_cast<SurfaceImpl *>(surface)->DrawLineMarker(rcWhole,
+ fontForCharacter,
+ tFold, data);
+}
+/** Custom function for drawing wrap markers in curses. */
+static void DrawWrapVisualMarker(Surface *surface, PRectangle rcPlace,
+ bool isEndMarker, ColourDesired wrapColour) {
+ reinterpret_cast<SurfaceImpl *>(surface)->DrawWrapMarker(rcPlace, isEndMarker,
+ wrapColour);
+}
+/** Custom function for drawing tab arrows in curses. */
+static void DrawTabArrow(Surface *surface, PRectangle rcTab, int ymid) {
+ reinterpret_cast<SurfaceImpl *>(surface)->DrawTabArrow(rcTab);
+}
+
+// Window handling.
+
+/** Deletes the window. */
+Window::~Window() {}
+/**
+ * Releases the window's resources.
+ * Since the only Windows created are AutoComplete and CallTip windows, and
+ * since those windows are created in `ListBox::Create()` and
+ * `ScintillaCurses::CreateCallTipWindow()`, respectively, via `newwin()`, it is
+ * safe to use `delwin()`.
+ * It is important to note that even though `ScintillaCurses::wMain` is a Window,
+ * its `Destroy()` function is never called, hence why `scintilla_delete()` is
+ * the complement to `scintilla_new()`.
+ */
+void Window::Destroy() {
+ if (wid) delwin(_WINDOW(wid));
+ wid = 0;
+}
+/**
+ * Returns the window's boundaries.
+ * Unlike other platforms, Scintilla paints in coordinates relative to the
+ * window in curses. Therefore, this function should always return the window
+ * bounds to ensure all of it is painted.
+ * @return PRectangle with the window's boundaries.
+ */
+PRectangle Window::GetPosition() {
+ int maxx = wid ? getmaxx(_WINDOW(wid)) : 0;
+ int maxy = wid ? getmaxy(_WINDOW(wid)) : 0;
+ return PRectangle(0, 0, maxx, maxy);
+}
+/**
+ * Sets the position of the window relative to its parent window.
+ * It will take care not to exceed the boundaries of the parent.
+ * @param rc The position relative to the parent window.
+ * @param relativeTo The parent window.
+ */
+void Window::SetPositionRelative(PRectangle rc, Window relativeTo) {
+ int begx = 0, begy = 0, x = 0, y = 0;
+ // Determine the relative position.
+ getbegyx(_WINDOW(relativeTo.GetID()), begy, begx);
+ x = begx + rc.left;
+ if (x < begx) x = begx;
+ y = begy + rc.top;
+ if (y < begy) y = begy;
+ // Correct to fit the parent if necessary.
+ int sizex = rc.right - rc.left;
+ int sizey = rc.bottom - rc.top;
+ int screen_width = getmaxx(_WINDOW(relativeTo.GetID()));
+ int screen_height = getmaxy(_WINDOW(relativeTo.GetID()));
+ if (sizex > screen_width)
+ x = begx; // align left
+ else if (x + sizex > begx + screen_width)
+ x = begx + screen_width - sizex; // align right
+ if (y + sizey > begy + screen_height) {
+ y = begy + screen_height - sizey; // align bottom
+ if (screen_height == 1) y--; // show directly above the relative window
+ }
+ if (y < 0) y = begy; // align top
+ // Update the location.
+ mvwin(_WINDOW(wid), y, x);
+}
+/** Identical to `Window::GetPosition()`. */
+PRectangle Window::GetClientPosition() { return GetPosition(); }
+void Window::Show(bool show) { /* TODO: */ }
+void Window::InvalidateAll() { /* notify repaint */ }
+void Window::InvalidateRectangle(PRectangle rc) { /* notify repaint*/ }
+/** Setting the font is not implemented. */
+void Window::SetFont(Font &) {}
+/** Setting the cursor icon is not implemented. */
+void Window::SetCursor(Cursor curs) {}
+/** Identical to `Window::GetPosition()`. */
+PRectangle Window::GetMonitorRect(Point pt) { return GetPosition(); }
+
+/**
+ * Implementation of a Scintilla ListBox for curses.
+ * Instead of registering images to types, printable UTF-8 characters are
+ * registered to types.
+ */
+class ListBoxImpl : public ListBox {
+ int height, width;
+ std::vector<std::string> list;
+ char types[IMAGE_MAX + 1][5]; // UTF-8 character plus terminating '\0'
+ int selection;
+public:
+ CallBackAction doubleClickAction;
+ void *doubleClickActionData;
+
+ /** Allocates a new Scintilla ListBox for curses. */
+ ListBoxImpl() : height(5), width(10), selection(0), doubleClickAction(NULL),
+ doubleClickActionData(NULL) {
+ list.reserve(10);
+ ClearRegisteredImages();
+ }
+ /** Deletes the ListBox. */
+ ~ListBoxImpl() {}
+
+ /** Setting the font is not implemented. */
+ void SetFont(Font &font) {}
+ /**
+ * Creates a new listbox.
+ * The `Show()` function resizes window with the appropriate height and width.
+ */
+ void Create(Window &parent, int ctrlID, Point location_, int lineHeight_,
+ bool unicodeMode_, int technology_) {
+ wid = newwin(1, 1, 0, 0);
+ }
+ /**
+ * Setting average char width is not implemented since all curses characters
+ * have a width of 1.
+ */
+ void SetAverageCharWidth(int width) {}
+ /** Sets the number of visible rows in the listbox. */
+ void SetVisibleRows(int rows) {
+ height = rows;
+ wresize(_WINDOW(wid), height + 2, width + 2);
+ }
+ /** Returns the number of visible rows in the listbox. */
+ int GetVisibleRows() const { return height; }
+ /** Returns the desired size of the listbox. */
+ PRectangle GetDesiredRect() {
+ return PRectangle(0, 0, width + 2, height + 2); // add border widths
+ }
+ /**
+ * Returns the left-offset of the ListBox with respect to the caret.
+ * Takes into account the border width and type character width.
+ * @return 2 to shift the ListBox to the left two characters.
+ */
+ int CaretFromEdge() { return 2; }
+ /** Clears the contents of the listbox. */
+ void Clear() {
+ list.clear();
+ width = 0;
+ }
+ /**
+ * Adds the given string list item to the listbox.
+ * Prepends the item's type character (if any) to the list item for display.
+ */
+ void Append(char *s, int type = -1) {
+ if (type >= 0 && type <= IMAGE_MAX) {
+ char *chtype = types[type];
+ list.push_back(std::string(chtype, strlen(chtype)) + std::string(s));
+ } else list.push_back(std::string(" ") + std::string(s));
+ int len = strlen(s); // TODO: UTF-8 awareness?
+ if (width < len) {
+ width = len + 1; // include type character len
+ wresize(_WINDOW(wid), height + 2, width + 2);
+ }
+ }
+ /** Returns the number of items in the listbox. */
+ int Length() { return list.size(); }
+ /** Selects the given item in the listbox and repaints the listbox. */
+ void Select(int n) {
+ WINDOW *w = _WINDOW(wid);
+ wclear(w);
+ box(w, '|', '-');
+ int len = static_cast<int>(list.size());
+ int s = n - height / 2;
+ if (s + height > len) s = len - height;
+ if (s < 0) s = 0;
+ for (int i = s; i < s + height && i < len; i++) {
+ mvwaddstr(w, i - s + 1, 1, list.at(i).c_str());
+ if (i == n) mvwchgat(w, i - s + 1, 2, width - 1, A_REVERSE, 0, NULL);
+ }
+ wmove(w, n - s + 1, 1); // place cursor on selected line
+ wnoutrefresh(w);
+ selection = n;
+ }
+ /** Returns the currently selected item in the listbox. */
+ int GetSelection() { return selection; }
+ /**
+ * Searches the listbox for the items matching the given prefix string and
+ * returns the index of the first match.
+ * Since the type is displayed as the first character, the value starts on the
+ * second character; match strings starting there.
+ */
+ int Find(const char *prefix) {
+ int len = strlen(prefix);
+ for (unsigned int i = 0; i < list.size(); i++) {
+ const char *item = list.at(i).c_str();
+ item += UTF8DrawBytes(reinterpret_cast<const unsigned char *>(item),
+ strlen(item));
+ if (strncmp(prefix, item, len) == 0) return i;
+ }
+ return -1;
+ }
+ /**
+ * Gets the item in the listbox at the given index and stores it in the given
+ * string.
+ * Since the type is displayed as the first character, the value starts on the
+ * second character.
+ */
+ void GetValue(int n, char *value, int len) {
+ if (len > 0) {
+ const char *item = list.at(n).c_str();
+ item += UTF8DrawBytes(reinterpret_cast<const unsigned char *>(item),
+ strlen(item));
+ strncpy(value, item, len);
+ value[len - 1] = '\0';
+ } else value[0] = '\0';
+ }
+ /**
+ * Registers the first UTF-8 character of the given string to the given type.
+ * By default, ' ' (space) is registered to all types.
+ * @usage SCI_REGISTERIMAGE(1, "*") // type 1 shows '*' in front of list item.
+ * @usage SCI_REGISTERIMAGE(2, "+") // type 2 shows '+' in front of list item.
+ * @usage SCI_REGISTERIMAGE(3, "■") // type 3 shows '■' in front of list item.
+ */
+ void RegisterImage(int type, const char *xpm_data) {
+ if (type < 0 || type > IMAGE_MAX) return;
+ int len = UTF8DrawBytes(reinterpret_cast<const unsigned char *>(xpm_data),
+ strlen(xpm_data));
+ for (int i = 0; i < len; i++) types[type][i] = xpm_data[i];
+ types[type][len] = '\0';
+ }
+ /** Registering images is not implemented. */
+ void RegisterRGBAImage(int type, int width, int height,
+ const unsigned char *pixelsImage) {}
+ /** Clears all registered types back to ' ' (space). */
+ void ClearRegisteredImages() {
+ for (int i = 0; i <= IMAGE_MAX; i++) types[i][0] = ' ', types[i][1] = '\0';
+ }
+ /** Enable double-click to select a list item. */
+ void SetDoubleClickAction(CallBackAction action, void *data) {
+ doubleClickAction = action, doubleClickActionData = data;
+ }
+ /** Sets the list items in the listbox to the given items. */
+ void SetList(const char *listText, char separator, char typesep) {
+ Clear();
+ int len = strlen(listText);
+ char *text = new char[len + 1];
+ if (!text) return;
+ memcpy(text, listText, len + 1);
+ char *word = text, *type = NULL;
+ for (int i = 0; i <= len; i++) {
+ if (text[i] == separator || i == len) {
+ text[i] = '\0';
+ if (type) *type = '\0';
+ Append(word, type ? atoi(type + 1) : -1);
+ word = text + i + 1, type = NULL;
+ } else if (text[i] == typesep)
+ type = text + i;
+ }
+ delete []text;
+ }
+};
+
+/** Creates a new Scintilla ListBox. */
+ListBox::ListBox() {}
+/** Deletes the ListBox. */
+ListBox::~ListBox() {}
+/** Creates a new curses ListBox. */
+ListBox *ListBox::Allocate() { return new ListBoxImpl(); }
+
+// Menus are not implemented.
+Menu::Menu() : mid(0) {}
+void Menu::CreatePopUp() {}
+void Menu::Destroy() {}
+void Menu::Show(Point pt, Window &w) {}
+
+// ElapsedTime is not implemented.
+ElapsedTime::ElapsedTime() : bigBit(0), littleBit(0) {}
+double ElapsedTime::Duration(bool reset) { return 1; }
+
+/** Dynamic library loading is not implemented. */
+DynamicLibrary *DynamicLibrary::Load(const char *modulePath) {
+ /* TODO */ return 0;
+}
+
+ColourDesired Platform::Chrome() { return ColourDesired(0, 0, 0); }
+ColourDesired Platform::ChromeHighlight() { return ColourDesired(0, 0, 0); }
+const char *Platform::DefaultFont() { return "monospace"; }
+int Platform::DefaultFontSize() { return 10; }
+unsigned int Platform::DoubleClickTime() { return 500; /* ms */ }
+bool Platform::MouseButtonBounce() { return true; }
+void Platform::DebugDisplay(const char *s) { fprintf(stderr, "%s", s); }
+//bool Platform::IsKeyDown(int key) { return false; }
+//long Platform::SendScintilla(WindowID w, unsigned int msg,
+// unsigned long wParam, long lParam) { return 0; }
+//long Platform::SendScintillaPointer(WindowID w, unsigned int msg,
+// unsigned long wParam,
+// void *lParam) { return 0; }
+//bool Platform::IsDBCSLeadByte(int codePage, char ch) { return false; }
+//int Platform::DBCSCharLength(int codePage, const char *s) {
+// int bytes = mblen(s, MB_CUR_MAX);
+// return (bytes >= 1) ? bytes : 1;
+//}
+//int Platform::DBCSCharMaxLength() { return MB_CUR_MAX; }
+int Platform::Minimum(int a, int b) { return (a < b) ? a : b; }
+int Platform::Maximum(int a, int b) { return (a > b) ? a : b; }
+void Platform::DebugPrintf(const char *format, ...) {}
+//bool Platform::ShowAssertionPopUps(bool assertionPopUps_) { return true; }
+void Platform::Assert(const char *c, const char *file, int line) {
+ char buffer[2000];
+ sprintf(buffer, "Assertion [%s] failed at %s %d\r\n", c, file, line);
+ Platform::DebugDisplay(buffer);
+ abort();
+}
+int Platform::Clamp(int val, int minVal, int maxVal) {
+ if (val > maxVal) val = maxVal;
+ if (val < minVal) val = minVal;
+ return val;
+}
+
+/** Implementation of Scintilla for curses. */
+class ScintillaCurses : public ScintillaBase {
+ Surface *sur; // window surface to draw on
+ int width, height; // window dimensions
+ void (*callback)(Scintilla *, int, void *, void *); // SCNotification callback
+ int scrollBarVPos, scrollBarHPos; // positions of the scroll bars
+ int scrollBarHeight, scrollBarWidth; // height and width of the scroll bars
+ SelectionText clipboard; // current clipboard text
+ bool capturedMouse; // whether or not the mouse is currently captured
+ unsigned int autoCompleteLastClickTime; // last click time in the AC box
+ bool draggingVScrollBar, draggingHScrollBar; // a scrollbar is being dragged
+ int dragOffset; // the distance to the position of the scrollbar being dragged
+
+ /**
+ * Uses the given UTF-8 code point to fill the given UTF-8 byte sequence and
+ * length.
+ * This algorithm was inspired by Paul Evans' libtermkey.
+ * (http://www.leonerd.org.uk/code/libtermkey)
+ * @param code The UTF-8 code point.
+ * @param s The string to write the UTF-8 byte sequence in. Must be at least
+ * 6 bytes in size.
+ * @param len The integer to put the number of UTF-8 bytes written in.
+ */
+ void toutf8(int code, char *s, int *len) {
+ if (code < 0x80) *len = 1;
+ else if (code < 0x800) *len = 2;
+ else if (code < 0x10000) *len = 3;
+ else if (code < 0x200000) *len = 4;
+ else if (code < 0x4000000) *len = 5;
+ else *len = 6;
+ for (int b = *len - 1; b > 0; b--) s[b] = 0x80 | (code & 0x3F), code >>= 6;
+ if (*len == 1) s[0] = code & 0x7F;
+ else if (*len == 2) s[0] = 0xC0 | (code & 0x1F);
+ else if (*len == 3) s[0] = 0xE0 | (code & 0x0F);
+ else if (*len == 4) s[0] = 0xF0 | (code & 0x07);
+ else if (*len == 5) s[0] = 0xF8 | (code & 0x03);
+ else if (*len == 6) s[0] = 0xFC | (code & 0x01);
+ }
+public:
+ /**
+ * Creates a new Scintilla instance in a curses `WINDOW`.
+ * However, the `WINDOW` itself will not be created until it is absolutely
+ * necessary. When the `WINDOW` is created, it will initially be full-screen.
+ * @param callback_ Callback function for Scintilla notifications.
+ */
+ ScintillaCurses(void (*callback_)(Scintilla *, int, void *, void *)) :
+ width(0), height(0), scrollBarHeight(1), scrollBarWidth(1) {
+ callback = callback_;
+ sur = Surface::Allocate(SC_TECHNOLOGY_DEFAULT);
+
+ // Defaults for curses.
+ marginView.wrapMarkerPaddingRight = 0; // no padding for margin wrap markers
+ marginView.customDrawWrapMarker = DrawWrapVisualMarker; // draw text markers
+ view.tabWidthMinimumPixels = 0; // no proportional fonts
+ view.drawOverstrikeCaret = false; // always draw normal caret
+ view.bufferedDraw = false; // draw directly to the screen
+ view.phasesDraw = EditView::phasesOne; // no need for two-phase drawing
+ view.tabArrowHeight = 0; // no additional tab arrow height
+ view.customDrawTabArrow = DrawTabArrow; // draw text arrows for tabs
+ view.customDrawWrapMarker = DrawWrapVisualMarker; // draw text wrap markers
+ mouseSelectionRectangularSwitch = true; // easier rectangular selection
+ doubleClickCloseThreshold = Point(0, 0); // double-clicks only in same cell
+ horizontalScrollBarVisible = false; // no horizontal scroll bar
+ scrollWidth = 5 * width; // reasonable default for any horizontal scroll bar
+ vs.selColours.fore = ColourDesired(0, 0, 0); // black on white selection
+ vs.selColours.fore.isSet = true; // setting selection foreground above
+ vs.caretcolour = ColourDesired(0xFF, 0xFF, 0xFF); // white caret
+ vs.caretStyle = CARETSTYLE_BLOCK; // block caret
+ vs.leftMarginWidth = 0, vs.rightMarginWidth = 0; // no margins
+ vs.ms[1].width = 1; // marker margin width should be 1
+ vs.extraDescent = -1; // hack to make lineHeight 1 instead of 2
+ // Set default marker foreground and background colors.
+ for (int i = 0; i <= MARKER_MAX; i++) {
+ vs.markers[i].fore = ColourDesired(0xC0, 0xC0, 0xC0);
+ vs.markers[i].back = ColourDesired(0, 0, 0);
+ if (i >= 25) vs.markers[i].markType = SC_MARK_EMPTY;
+ vs.markers[i].customDraw = DrawLineMarker;
+ }
+ // Use '+' and '-' fold markers.
+ vs.markers[SC_MARKNUM_FOLDEROPEN].markType = SC_MARK_MINUS;
+ vs.markers[SC_MARKNUM_FOLDER].markType = SC_MARK_PLUS;
+ vs.markers[SC_MARKNUM_FOLDEROPENMID].markType = SC_MARK_MINUS;
+ vs.markers[SC_MARKNUM_FOLDEREND].markType = SC_MARK_PLUS;
+ displayPopupMenu = false; // no context menu
+ vs.marginNumberPadding = 0; // no number margin padding
+ vs.ctrlCharPadding = 0; // no ctrl character text blob padding
+ vs.lastSegItalicsOffset = 0; // no offset for italic characters at EOLs
+ ac.widthLBDefault = 10; // more sane bound for autocomplete width
+ ac.heightLBDefault = 10; // more sane bound for autocomplete height
+ ct.colourBG = ColourDesired(0, 0, 0); // black background color
+ ct.colourUnSel = ColourDesired(0xC0, 0xC0, 0xC0); // white text
+ ct.insetX = 2; // border and arrow widths are 1 each
+ ct.widthArrow = 1; // arrow width is 1 character
+ ct.borderHeight = 1; // no extra empty lines in border height
+ ct.verticalOffset = 0; // no extra offset of calltip from line
+ }
+ /** Deletes the Scintilla instance. */
+ ~ScintillaCurses() {
+ if (wMain.GetID())
+ delwin(GetWINDOW());
+ if (sur) {
+ sur->Release();
+ delete sur;
+ }
+ }
+ /** Initializing code is unnecessary. */
+ void Initialise() {}
+ /** Disable drag and drop since it is not implemented. */
+ void StartDrag() {
+ inDragDrop = ddNone;
+ SetDragPosition(SelectionPosition(Sci::invalidPosition));
+ }
+ /** Draws the vertical scroll bar. */
+ void SetVerticalScrollPos() {
+ if (!wMain.GetID() || !verticalScrollBarVisible) return;
+ WINDOW *w = GetWINDOW();
+ int maxy = getmaxy(w), maxx = getmaxx(w);
+ // Draw the gutter.
+ wattr_set(w, 0, term_color_pair(COLOR_WHITE, COLOR_BLACK), NULL);
+ for (int i = 0; i < maxy; i++) mvwaddch(w, i, maxx - 1, ACS_CKBOARD);
+ // Draw the bar.
+ scrollBarVPos = static_cast<float>(topLine) /
+ (MaxScrollPos() + LinesOnScreen() - 1) * maxy;
+ wattr_set(w, 0, term_color_pair(COLOR_BLACK, COLOR_WHITE), NULL);
+ for (int i = scrollBarVPos; i < scrollBarVPos + scrollBarHeight; i++)
+ mvwaddch(w, i, maxx - 1, ' ');
+ }
+ /** Draws the horizontal scroll bar. */
+ void SetHorizontalScrollPos() {
+ if (!wMain.GetID() || !horizontalScrollBarVisible) return;
+ WINDOW *w = GetWINDOW();
+ int maxy = getmaxy(w), maxx = getmaxx(w);
+ // Draw the gutter.
+ wattr_set(w, 0, term_color_pair(COLOR_WHITE, COLOR_BLACK), NULL);
+ for (int i = 0; i < maxx; i++) mvwaddch(w, maxy - 1, i, ACS_CKBOARD);
+ // Draw the bar.
+ scrollBarHPos = static_cast<float>(xOffset) / scrollWidth * maxx;
+ wattr_set(w, 0, term_color_pair(COLOR_BLACK, COLOR_WHITE), NULL);
+ for (int i = scrollBarHPos; i < scrollBarHPos + scrollBarWidth; i++)
+ mvwaddch(w, maxy - 1, i, ' ');
+ }
+ /**
+ * Sets the height of the vertical scroll bar and width of the horizontal
+ * scroll bar.
+ * The height is based on the given size of a page and the total number of
+ * pages. The width is based on the width of the view and the view's scroll
+ * width property.
+ */
+ bool ModifyScrollBars(int nMax, int nPage) {
+ if (!wMain.GetID()) return false;
+ WINDOW *w = GetWINDOW();
+ int maxy = getmaxy(w), maxx = getmaxx(w);
+ int height = roundf(static_cast<float>(nPage) / nMax * maxy);
+ scrollBarHeight = Platform::Clamp(height, 1, maxy);
+ int width = roundf(static_cast<float>(maxx) / scrollWidth * maxx);
+ scrollBarWidth = Platform::Clamp(width, 1, maxx);
+ return true;
+ }
+ /**
+ * Copies the selected text to the internal clipboard.
+ * The primary and secondary X selections are unaffected.
+ */
+ void Copy() { if (!sel.Empty()) CopySelectionRange(&clipboard); }
+ /**
+ * Pastes text from the internal clipboard, not from primary or secondary X
+ * selections.
+ */
+ void Paste() {
+ if (clipboard.Empty()) return;
+ ClearSelection(multiPasteMode == SC_MULTIPASTE_EACH);
+ InsertPasteShape(clipboard.Data(), static_cast<int>(clipboard.Length()),
+ !clipboard.rectangular ? pasteStream : pasteRectangular);
+ EnsureCaretVisible();
+ }
+ /** Setting of the primary and/or secondary X selections is not supported. */
+ void ClaimSelection() {}
+ /** Notifying the parent of text changes is not yet supported. */
+ void NotifyChange() {}
+ /** Send Scintilla notifications to the parent. */
+ void NotifyParent(SCNotification scn) {
+ if (callback)
+ (*callback)(reinterpret_cast<Scintilla *>(this), 0, (void *)&scn, 0);
+ }
+ /**
+ * Handles an unconsumed key.
+ * If a character is being typed, add it to the editor. Otherwise, notify the
+ * container.
+ */
+ int KeyDefault(int key, int modifiers) {
+ if ((IsUnicodeMode() || key < 256) && modifiers == 0) {
+ if (IsUnicodeMode()) {
+ char utf8[6];
+ int len;
+ toutf8(key, utf8, &len);
+ return (AddCharUTF(utf8, len), 1);
+ } else return (AddChar(key), 1);
+ } else {
+ SCNotification scn = {};
+ scn.nmhdr.code = SCN_KEY;
+ scn.ch = key;
+ scn.modifiers = modifiers;
+ return (NotifyParent(scn), 0);
+ }
+ }
+ /**
+ * Copies the given text to the internal clipboard.
+ * Like `Copy()`, does not affect the primary and secondary X selections.
+ */
+ void CopyToClipboard(const SelectionText &selectedText) {
+ clipboard.Copy(selectedText);
+ }
+ /** A ticking caret is not implemented. */
+ void SetTicking(bool on) {}
+ /**
+ * Sets whether or not the mouse is captured.
+ * This is used by Scintilla to handle mouse clicks, drags, and releases.
+ */
+ void SetMouseCapture(bool on) { capturedMouse = on; }
+ /** Returns whether or not the mouse is captured. */
+ bool HaveMouseCapture() { return capturedMouse; }
+ /** A Scintilla direct pointer is not implemented. */
+ sptr_t DefWndProc(unsigned int iMessage, uptr_t wParam, sptr_t lParam) {
+ return 0;
+ }
+ /** Draws a CallTip, creating the curses window for it if necessary. */
+ void CreateCallTipWindow(PRectangle rc) {
+ if (!wMain.GetID()) return;
+ if (!ct.wCallTip.Created()) {
+ rc.right -= 1; // remove right-side padding
+ int begx = 0, begy = 0, maxx = 0, maxy = 0;
+ getbegyx(GetWINDOW(), begy, begx);
+ int xoffset = begx - rc.left, yoffset = begy - rc.top;
+ if (xoffset > 0) rc.left += xoffset, rc.right += xoffset;
+ if (yoffset > 0) rc.top += yoffset, rc.bottom += yoffset;
+ getmaxyx(GetWINDOW(), maxy, maxx);
+ if (rc.Width() > maxx) rc.right = rc.left + maxx;
+ if (rc.Height() > maxy) rc.bottom = rc.top + maxy;
+ ct.wCallTip = newwin(rc.Height(), rc.Width(), rc.top, rc.left);
+ }
+ WindowID wid = ct.wCallTip.GetID();
+ box(_WINDOW(wid), '|', '-');
+ Surface *sur = Surface::Allocate(SC_TECHNOLOGY_DEFAULT);
+ if (sur) {
+ sur->Init(wid);
+ ct.PaintCT(sur);
+ wnoutrefresh(_WINDOW(wid));
+ sur->Release();
+ delete sur;
+ }
+ }
+ /** Adding menu items to the popup menu is not implemented. */
+ void AddToPopUp(const char *label, int cmd=0, bool enabled=true) {}
+ /**
+ * Sends the given message and parameters to Scintilla unless it is a message
+ * that changes an unsupported property.
+ */
+ sptr_t WndProc(unsigned int iMessage, uptr_t wParam, sptr_t lParam) {
+ try {
+ switch (iMessage) {
+ case SCI_GETDIRECTFUNCTION:
+ return reinterpret_cast<sptr_t>(scintilla_send_message);
+ case SCI_GETDIRECTPOINTER: return reinterpret_cast<sptr_t>(this);
+ // Ignore attempted changes of the following unsupported properties.
+ case SCI_SETBUFFEREDDRAW:
+ case SCI_SETWHITESPACESIZE:
+ case SCI_SETTWOPHASEDRAW: case SCI_SETPHASESDRAW:
+ case SCI_SETEXTRAASCENT: case SCI_SETEXTRADESCENT:
+ return 0;
+ // Pass to Scintilla.
+ default: return ScintillaBase::WndProc(iMessage, wParam, lParam);
+ }
+ } catch (std::bad_alloc&) {
+ errorStatus = SC_STATUS_BADALLOC;
+ } catch (...) {
+ errorStatus = SC_STATUS_FAILURE;
+ }
+ return 0;
+ }
+
+ /**
+ * Returns the curses `WINDOW` associated with this Scintilla instance.
+ * If the `WINDOW` has not been created yet, create it now.
+ */
+ WINDOW *GetWINDOW() {
+ if (!wMain.GetID()) {
+ init_colors();
+ wMain = newwin(0, 0, 0, 0);
+ WINDOW *w = _WINDOW(wMain.GetID());
+ keypad(w, TRUE);
+ if (sur)
+ sur->Init(w);
+ getmaxyx(w, height, width);
+ InvalidateStyleRedraw(); // needed to fully initialize Scintilla
+ }
+ return _WINDOW(wMain.GetID());
+ }
+ /**
+ * Repaints the Scintilla window on the virtual screen.
+ * If an autocompletion list, user list, or calltip is active, redraw it over
+ * the buffer's contents.
+ * It is the application's responsibility to call the curses `doupdate()` in
+ * order to refresh the physical screen.
+ * To paint to the physical screen instead, use `Refresh()`.
+ * @see Refresh
+ */
+ void NoutRefresh() {
+ WINDOW *w = GetWINDOW();
+ rcPaint.top = 0, rcPaint.left = 0; // paint from (0, 0), not (begy, begx)
+ getmaxyx(w, rcPaint.bottom, rcPaint.right);
+ if (rcPaint.bottom != height || rcPaint.right != width)
+ height = rcPaint.bottom, width = rcPaint.right, ChangeSize();
+ Paint(sur, rcPaint);
+ SetVerticalScrollPos(), SetHorizontalScrollPos();
+ wnoutrefresh(w);
+#if PDCURSES
+ touchwin(w); // pdcurses sometimes has problems drawing overlapping windows
+#endif
+ if (ac.Active())
+ ac.lb->Select(ac.lb->GetSelection()); // redraw
+ else if (ct.inCallTipMode)
+ CreateCallTipWindow(PRectangle(0, 0, 0, 0)); // redraw
+ if (hasFocus) {
+ // Update cursor position, even if it's not visible, as the container may
+ // have a use for it.
+ int pos = WndProc(SCI_GETCURRENTPOS, 0, 0);
+ move(getbegy(w) + WndProc(SCI_POINTYFROMPOSITION, 0, pos),
+ getbegx(w) + WndProc(SCI_POINTXFROMPOSITION, 0, pos));
+ }
+ }
+ /**
+ * Repaints the Scintilla window on the physical screen.
+ * If an autocompletion list, user list, or calltip is active, redraw it over
+ * the buffer's contents.
+ * To paint to the virtual screen instead, use `NoutRefresh()`.
+ * @see NoutRefresh
+ */
+ void Refresh() {
+ NoutRefresh();
+ doupdate();
+ }
+ /**
+ * Sends a key to Scintilla.
+ * Usually if a key is consumed, the screen should be repainted. However, when
+ * autocomplete is active, that window is consuming the keys and any
+ * repainting of the main Scintilla window will overwrite the autocomplete
+ * window.
+ * @param key The key pressed.
+ * @param shift Flag indicating whether or not the shift modifier key is
+ * pressed.
+ * @param shift Flag indicating whether or not the control modifier key is
+ * pressed.
+ * @param shift Flag indicating whether or not the alt modifier key is
+ * pressed.
+ */
+ void KeyPress(int key, bool shift, bool ctrl, bool alt) {
+ KeyDown(key, shift, ctrl, alt, NULL);
+ }
+ /**
+ * Handles a mouse button press.
+ * @param button The button number pressed, or `0` if none.
+ * @param time The time in milliseconds of the mouse event.
+ * @param y The y coordinate of the mouse event relative to this window.
+ * @param x The x coordinate of the mouse event relative to this window.
+ * @param shift Flag indicating whether or not the shift modifier key is
+ * pressed.
+ * @param ctrl Flag indicating whether or not the control modifier key is
+ * pressed.
+ * @param alt Flag indicating whether or not the alt modifier key is pressed.
+ * @return whether or not the mouse event was handled
+ */
+ bool MousePress(int button, unsigned int time, int y, int x, bool shift,
+ bool ctrl, bool alt) {
+ GetWINDOW(); // ensure the curses `WINDOW` has been created
+ if (ac.Active() && (button == 1 || button == 4 || button == 5)) {
+ // Select an autocompletion list item if possible or scroll the list.
+ WINDOW *w = _WINDOW(ac.lb->GetID()), *parent = GetWINDOW();
+ int begy = getbegy(w) - getbegy(parent); // y is relative to the view
+ int begx = getbegx(w) - getbegx(parent); // x is relative to the view
+ int maxy = getmaxy(w) - 1, maxx = getmaxx(w) - 1; // ignore border
+ int ry = y - begy, rx = x - begx; // relative to list box
+ if (ry > 0 && ry < maxy && rx > 0 && rx < maxx) {
+ if (button == 1) {
+ // Select a list item.
+ // The currently selected item is normally displayed in the middle.
+ int middle = ac.lb->GetVisibleRows() / 2;
+ int n = ac.lb->GetSelection(), ny = middle;
+ if (n < middle)
+ ny = n; // the currently selected item is near the beginning
+ else if (n >= ac.lb->Length() - middle)
+ ny = (n - 1) % ac.lb->GetVisibleRows(); // it's near the end
+ // Compute the index of the item to select.
+ int offset = ry - ny - 1; // -1 ignores list box border
+ if (offset == 0 &&
+ time - autoCompleteLastClickTime < Platform::DoubleClickTime()) {
+ ListBoxImpl* listbox = reinterpret_cast<ListBoxImpl *>(ac.lb.get());
+ if (listbox->doubleClickAction != NULL)
+ listbox->doubleClickAction(listbox->doubleClickActionData);
+ } else ac.lb->Select(n + offset);
+ autoCompleteLastClickTime = time;
+ } else {
+ // Scroll the list.
+ int n = ac.lb->GetSelection();
+ if (button == 4 && n > 0)
+ ac.lb->Select(n - 1);
+ else if (button == 5 && n < ac.lb->Length() - 1)
+ ac.lb->Select(n + 1);
+ }
+ return true;
+ } else if (ry == 0 || ry == maxy || rx == 0 || rx == maxx)
+ return true; // ignore border click
+ } else if (ct.inCallTipMode && button == 1) {
+ // Send the click to the CallTip.
+ WINDOW *w = _WINDOW(ct.wCallTip.GetID()), *parent = GetWINDOW();
+ int begy = getbegy(w) - getbegy(parent); // y is relative to the view
+ int begx = getbegx(w) - getbegx(parent); // x is relative to the view
+ int maxy = getmaxy(w) - 1, maxx = getmaxx(w) - 1; // ignore border
+ int ry = y - begy, rx = x - begx; // relative to list box
+ if (ry >= 0 && ry <= maxy && rx >= 0 && rx <= maxx) {
+ ct.MouseClick(Point(rx, ry));
+ return (CallTipClick(), true);
+ }
+ }
+
+ if (button == 1) {
+ if (verticalScrollBarVisible && x == getmaxx(GetWINDOW()) - 1) {
+ // Scroll the vertical scrollbar.
+ if (y < scrollBarVPos)
+ return (ScrollTo(topLine - LinesOnScreen()), true);
+ else if (y >= scrollBarVPos + scrollBarHeight)
+ return (ScrollTo(topLine + LinesOnScreen()), true);
+ else
+ draggingVScrollBar = true, dragOffset = y - scrollBarVPos;
+ } else if (horizontalScrollBarVisible && y == getmaxy(GetWINDOW()) - 1) {
+ // Scroll the horizontal scroll bar.
+ if (x < scrollBarHPos)
+ return (HorizontalScrollTo(xOffset - getmaxx(GetWINDOW()) / 2), true);
+ else if (x >= scrollBarHPos + scrollBarWidth)
+ return (HorizontalScrollTo(xOffset + getmaxx(GetWINDOW()) / 2), true);
+ else
+ draggingHScrollBar = true, dragOffset = x - scrollBarHPos;
+ } else {
+ // Have Scintilla handle the click.
+ ButtonDown(Point(x, y), time, shift, ctrl, alt);
+ return true;
+ }
+ } else if (button == 4 || button == 5) {
+ // Scroll the view.
+ int lines = getmaxy(GetWINDOW()) / 4;
+ if (lines < 1) lines = 1;
+ if (button == 4) lines *= -1;
+ return (ScrollTo(topLine + lines), true);
+ }
+ return false;
+ }
+ /**
+ * Sends a mouse move event to Scintilla, returning whether or not Scintilla
+ * handled the mouse event.
+ * @param y The y coordinate of the mouse event relative to this window.
+ * @param x The x coordinate of the mouse event relative to this window.
+ * @param shift Flag indicating whether or not the shift modifier key is
+ * pressed.
+ * @param ctrl Flag indicating whether or not the control modifier key is
+ * pressed.
+ * @param alt Flag indicating whether or not the alt modifier key is pressed.
+ * @return whether or not Scintilla handled the mouse event
+ */
+ bool MouseMove(int y, int x, bool shift, bool ctrl, bool alt) {
+ GetWINDOW(); // ensure the curses `WINDOW` has been created
+ if (!draggingVScrollBar && !draggingHScrollBar) {
+ int modifiers = (shift ? SCI_SHIFT : 0) | (ctrl ? SCI_CTRL : 0) |
+ (alt ? SCI_ALT : 0);
+ ButtonMoveWithModifiers(Point(x, y), modifiers);
+ } else if (draggingVScrollBar) {
+ int maxy = getmaxy(GetWINDOW()) - scrollBarHeight, pos = y - dragOffset;
+ if (pos >= 0 && pos <= maxy) ScrollTo(pos * MaxScrollPos() / maxy);
+ return true;
+ } else if (draggingHScrollBar) {
+ int maxx = getmaxx(GetWINDOW()) - scrollBarWidth, pos = x - dragOffset;
+ if (pos >= 0 && pos <= maxx)
+ HorizontalScrollTo(pos * (scrollWidth - maxx - scrollBarWidth) / maxx);
+ return true;
+ }
+ return HaveMouseCapture();
+ }
+ /**
+ * Sends a mouse release event to Scintilla.
+ * @param time The time in milliseconds of the mouse event.
+ * @param y The y coordinate of the mouse event relative to this window.
+ * @param x The x coordinate of the mouse event relative to this window.
+ * @param ctrl Flag indicating whether or not the control modifier key is
+ * pressed.
+ */
+ void MouseRelease(int time, int y, int x, int ctrl) {
+ GetWINDOW(); // ensure the curses `WINDOW` has been created
+ if (draggingVScrollBar || draggingHScrollBar)
+ draggingVScrollBar = false, draggingHScrollBar = false;
+ else if (HaveMouseCapture()) {
+ ButtonUp(Point(x, y), time, ctrl);
+ }
+ }
+ /**
+ * Copies the text of the internal clipboard, not the primary and/or secondary
+ * X selections, into the given buffer and returns the size of the clipboard
+ * text.
+ * @param text The buffer to copy clipboard text to.
+ * @return size of the clipboard text
+ */
+ int GetClipboard(char *buffer) {
+ if (buffer) memcpy(buffer, clipboard.Data(), clipboard.Length() + 1);
+ return clipboard.Length() + 1;
+ }
+};
+
+// Link with C. Documentation in Scintilla.h.
+extern "C" {
+Scintilla *scintilla_new(void (*callback)(Scintilla *, int, void *, void *)) {
+ return reinterpret_cast<Scintilla *>(new ScintillaCurses(callback));
+}
+WINDOW *scintilla_get_window(Scintilla *sci) {
+ return reinterpret_cast<ScintillaCurses *>(sci)->GetWINDOW();
+}
+sptr_t scintilla_send_message(Scintilla *sci, unsigned int iMessage,
+ uptr_t wParam, sptr_t lParam) {
+ return reinterpret_cast<ScintillaCurses *>(sci)->WndProc(iMessage, wParam,
+ lParam);
+}
+void scintilla_send_key(Scintilla *sci, int key, bool shift, bool ctrl,
+ bool alt) {
+ reinterpret_cast<ScintillaCurses *>(sci)->KeyPress(key, shift, ctrl, alt);
+}
+bool scintilla_send_mouse(Scintilla *sci, int event, unsigned int time,
+ int button, int y, int x, bool shift, bool ctrl,
+ bool alt) {
+ ScintillaCurses *scicurses = reinterpret_cast<ScintillaCurses *>(sci);
+ WINDOW *w = scicurses->GetWINDOW();
+ int begy = getbegy(w), begx = getbegx(w);
+ int maxy = getmaxy(w), maxx = getmaxx(w);
+ // Ignore most events outside the window.
+ if ((x < begx || x > begx + maxx - 1 || y < begy || y > begy + maxy - 1) &&
+ button != 4 && button != 5) return false;
+ y = y - begy, x = x - begx;
+ if (event == SCM_PRESS)
+ return scicurses->MousePress(button, time, y, x, shift, ctrl, alt);
+ else if (event == SCM_DRAG)
+ return scicurses->MouseMove(y, x, shift, ctrl, alt);
+ else if (event == SCM_RELEASE)
+ return (scicurses->MouseRelease(time, y, x, ctrl), true);
+ return false;
+}
+int scintilla_get_clipboard(Scintilla *sci, char *buffer) {
+ return reinterpret_cast<ScintillaCurses *>(sci)->GetClipboard(buffer);
+}
+void scintilla_noutrefresh(Scintilla *sci) {
+ reinterpret_cast<ScintillaCurses *>(sci)->NoutRefresh();
+}
+void scintilla_refresh(Scintilla *sci) {
+ reinterpret_cast<ScintillaCurses *>(sci)->Refresh();
+}
+void scintilla_delete(Scintilla *sci) {
+ delete reinterpret_cast<ScintillaCurses *>(sci);
+}
+}
diff --git a/curses/ScintillaCurses.h b/curses/ScintillaCurses.h
new file mode 100644
index 000000000..620982c2d
--- /dev/null
+++ b/curses/ScintillaCurses.h
@@ -0,0 +1,131 @@
+// Copyright 2012-2018 Mitchell mitchell.att.foicica.com. See License.txt.
+// Header for Scintilla in a curses (terminal) environment.
+
+#ifndef SCINTILLACURSES_H
+#define SCINTILLACURSES_H
+
+#include <curses.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef void *Scintilla;
+/**
+ * Creates a new Scintilla window.
+ * Curses does not have to be initialized before calling this function.
+ * @param callback A callback function for Scintilla notifications.
+ */
+Scintilla *scintilla_new(void (*callback)(Scintilla *sci, int iMessage,
+ void *wParam, void *lParam));
+/**
+ * Returns the curses `WINDOW` associated with the given Scintilla window.
+ * Curses must have been initialized prior to calling this function.
+ * @param sci The Scintilla window returned by `scintilla_new()`.
+ * @return curses `WINDOW`.
+ */
+WINDOW *scintilla_get_window(Scintilla *sci);
+/**
+ * Sends the given message with parameters to the given Scintilla window.
+ * Curses does not have to be initialized before calling this function.
+ * @param sci The Scintilla window returned by `scintilla_new()`.
+ * @param iMessage The message ID.
+ * @param wParam The first parameter.
+ * @param lParam The second parameter.
+ */
+sptr_t scintilla_send_message(Scintilla *sci, unsigned int iMessage,
+ uptr_t wParam, sptr_t lParam);
+/**
+ * Sends the specified key to the given Scintilla window for processing.
+ * If it is not consumed, an SCNotification will be emitted.
+ * Curses does not have to be initialized before calling this function.
+ * @param sci The Scintilla window returned by `scintilla_new()`.
+ * @param key The keycode of the key.
+ * @param shift Flag indicating whether or not the shift modifier key is
+ * pressed.
+ * @param ctrl Flag indicating whether or not the control modifier key is
+ * pressed.
+ * @param alt Flag indicating whether or not the alt modifier key is pressed.
+ */
+void scintilla_send_key(Scintilla *sci, int key, bool shift, bool ctrl,
+ bool alt);
+/**
+ * Sends the specified mouse event to the given Scintilla window for processing.
+ * Curses must have been initialized prior to calling this function.
+ * @param sci The Scintilla window returned by `scintilla_new()`.
+ * @param event The mouse event (`SCM_CLICK`, `SCM_DRAG`, or `SCM_RELEASE`).
+ * @param time The time in milliseconds of the mouse event. This is only needed
+ * if double and triple clicks need to be detected.
+ * @param button The button number pressed, or `0` if none.
+ * @param y The absolute y coordinate of the mouse event.
+ * @param x The absolute x coordinate of the mouse event.
+ * @param shift Flag indicating whether or not the shift modifier key is
+ * pressed.
+ * @param ctrl Flag indicating whether or not the control modifier key is
+ * pressed.
+ * @param alt Flag indicating whether or not the alt modifier key is pressed.
+ * @return whether or not Scintilla handled the mouse event
+ */
+bool scintilla_send_mouse(Scintilla *sci, int event, unsigned int time,
+ int button, int y, int x, bool shift, bool ctrl,
+ bool alt);
+/**
+ * Copies the text of Scintilla's internal clipboard, not the primary and/or
+ * secondary X selections, into the given buffer and returns the size of the
+ * clipboard text.
+ * Call with a `null` buffer first to get the size of the buffer needed to store
+ * clipboard text.
+ * Keep in mind clipboard text may contain null bytes.
+ * Curses does not have to be initialized before calling this function.
+ * @param sci The Scintilla window returned by `scintilla_new()`.
+ * @param buffer The buffer to copy clipboard text to.
+ * @return size of the clipboard text.
+ */
+int scintilla_get_clipboard(Scintilla *sci, char *buffer);
+/**
+ * Refreshes the Scintilla window on the virtual screen.
+ * This should be done along with the normal curses `noutrefresh()`, as the
+ * virtual screen is updated when calling this function.
+ * Curses must have been initialized prior to calling this function.
+ * @param sci The Scintilla window returned by `scintilla_new()`.
+ */
+void scintilla_noutrefresh(Scintilla *sci);
+/**
+ * Refreshes the Scintilla window on the physical screen.
+ * This should be done along with the normal curses `refresh()`, as the physical
+ * screen is updated when calling this function.
+ * Curses must have been initialized prior to calling this function.
+ * @param sci The Scintilla window returned by `scintilla_new()`.
+ */
+void scintilla_refresh(Scintilla *sci);
+/**
+ * Deletes the given Scintilla window.
+ * Curses must have been initialized prior to calling this function.
+ * @param sci The Scintilla window returned by `scintilla_new()`.
+ */
+void scintilla_delete(Scintilla *sci);
+
+/**
+ * Returns the curses `COLOR_PAIR` for the given curses foreground and
+ * background `COLOR`s.
+ * This is used simply to enumerate every possible color combination.
+ * Note: only 256 combinations are possible due to curses portability.
+ * Note: This references the global curses variable `COLORS` and is
+ * not a constant expression.
+ * @param f The curses foreground `COLOR`.
+ * @param b The curses background `COLOR`.
+ * @return int number for defining a curses `COLOR_PAIR`.
+ */
+#define SCI_COLOR_PAIR(f, b) ((b) * ((COLORS < 16) ? 8 : 16) + (f) + 1)
+
+#define IMAGE_MAX 31
+
+#define SCM_PRESS 1
+#define SCM_DRAG 2
+#define SCM_RELEASE 3
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/curses/THANKS.md b/curses/THANKS.md
new file mode 100644
index 000000000..23e7b0845
--- /dev/null
+++ b/curses/THANKS.md
@@ -0,0 +1,14 @@
+# Thanks
+
+Thank you everyone for your contributions and feedback. They have helped improve
+Scintilla curses.
+
+## Code Contributors
+
+* Chad Voegele
+* Chris Emerson
+* Neil Hodgson
+* Paul Evans
+* Peter Kazmier
+* Robin Haberkorn
+* Valere Monseur
diff --git a/curses/jinx/Makefile b/curses/jinx/Makefile
new file mode 100644
index 000000000..6811d4f7e
--- /dev/null
+++ b/curses/jinx/Makefile
@@ -0,0 +1,19 @@
+# Copyright 2012-2018 Mitchell mitchell.att.foicica.com. See License.txt.
+
+CC = gcc
+CXX = g++
+INCLUDEDIRS = -I ../../include -I ../../src -I ../../lexlib -I ../
+CFLAGS = -DCURSES -DSCI_LEXER -D_XOPEN_SOURCE_EXTENDED -W -Wall $(INCLUDEDIRS) \
+ -Wno-unused-parameter
+CXXFLAGS = $(CFLAGS)
+
+scintilla = ../../bin/scintilla_curses.a
+lexers = $(wildcard ../Lex*.o)
+
+all: jinx
+jinx.o: jinx.c
+ $(CC) $(CFLAGS) -c $<
+jinx: jinx.o $(lexers) $(scintilla)
+ $(CXX) -DCURSES $^ -o $@ -lncursesw
+clean:
+ rm -f jinx *.o
diff --git a/curses/jinx/jinx.c b/curses/jinx/jinx.c
new file mode 100644
index 000000000..83a955cb4
--- /dev/null
+++ b/curses/jinx/jinx.c
@@ -0,0 +1,106 @@
+// Copyright 2012-2018 Mitchell mitchell.att.foicica.com. See License.txt.
+
+#include <locale.h>
+#include <sys/time.h>
+#include <curses.h>
+
+#include "Scintilla.h"
+#include "SciLexer.h"
+#include "ScintillaCurses.h"
+
+#define SSM(m, w, l) scintilla_send_message(sci, m, w, l)
+
+void scnotification(Scintilla *view, int msg, void *lParam, void *wParam) {
+ //struct SCNotification *scn = (struct SCNotification *)lParam;
+ //printw("SCNotification received: %i", scn->nmhdr.code);
+}
+
+int main(int argc, char **argv) {
+ setlocale(LC_CTYPE, ""); // for displaying UTF-8 characters properly
+ initscr(), raw(), cbreak(), noecho(), start_color();
+ Scintilla *sci = scintilla_new(scnotification);
+ curs_set(0); // Scintilla draws its own cursor
+
+ SSM(SCI_STYLESETFORE, STYLE_DEFAULT, 0xFFFFFF);
+ SSM(SCI_STYLESETBACK, STYLE_DEFAULT, 0);
+ SSM(SCI_STYLECLEARALL, 0, 0);
+ SSM(SCI_SETLEXER, SCLEX_CPP, 0);
+ SSM(SCI_SETKEYWORDS, 0, (sptr_t)"int char");
+ SSM(SCI_STYLESETFORE, SCE_C_COMMENT, 0x00FF00);
+ SSM(SCI_STYLESETFORE, SCE_C_COMMENTLINE, 0x00FF00);
+ SSM(SCI_STYLESETFORE, SCE_C_NUMBER, 0xFFFF00);
+ SSM(SCI_STYLESETFORE, SCE_C_WORD, 0xFF0000);
+ SSM(SCI_STYLESETFORE, SCE_C_STRING, 0xFF00FF);
+ SSM(SCI_STYLESETBOLD, SCE_C_OPERATOR, 1);
+ SSM(SCI_INSERTTEXT, 0, (sptr_t)
+ "int main(int argc, char **argv) {\n"
+ " // Start up the gnome\n"
+ " gnome_init(\"stest\", \"1.0\", argc, argv);\n}");
+ SSM(SCI_SETPROPERTY, (uptr_t)"fold", (sptr_t)"1");
+ SSM(SCI_SETMARGINWIDTHN, 2, 1);
+ SSM(SCI_SETMARGINMASKN, 2, SC_MASK_FOLDERS);
+ SSM(SCI_SETMARGINSENSITIVEN, 2, 1);
+ SSM(SCI_SETAUTOMATICFOLD, SC_AUTOMATICFOLD_CLICK, 0);
+ SSM(SCI_SETFOCUS, 1, 0);
+ scintilla_refresh(sci);
+
+ printf("\033[?1000h"); // enable mouse press and release events
+ //printf("\033[?1002h"); // enable mouse press, drag, and release events
+ //printf("\033[?1003h"); // enable mouse move, press, drag, and release events
+ mousemask(ALL_MOUSE_EVENTS, NULL);
+ mouseinterval(0);
+
+ // Non-UTF8 input.
+ int c = 0;
+ MEVENT mouse;
+ WINDOW *win = scintilla_get_window(sci);
+ while ((c = wgetch(win)) != 'q') {
+ if (c != KEY_MOUSE) {
+ if (c == KEY_UP) c = SCK_UP;
+ else if (c == KEY_DOWN) c = SCK_DOWN;
+ else if (c == KEY_LEFT) c = SCK_LEFT;
+ else if (c == KEY_RIGHT) c = SCK_RIGHT;
+ scintilla_send_key(sci, c, FALSE, FALSE, FALSE);
+ } else if (getmouse(&mouse) == OK) {
+ int event = SCM_DRAG, button = 0;
+ if (mouse.bstate & BUTTON1_PRESSED)
+ event = SCM_PRESS, button = 1;
+ else if (mouse.bstate & BUTTON1_RELEASED)
+ event = SCM_RELEASE, button = 1;
+ struct timeval time = {0, 0};
+ gettimeofday(&time, NULL);
+ int millis = time.tv_sec * 1000 + time.tv_usec / 1000;
+ scintilla_send_mouse(sci, event, millis, button, mouse.y, mouse.x,
+ mouse.bstate & BUTTON_SHIFT,
+ mouse.bstate & BUTTON_CTRL,
+ mouse.bstate & BUTTON_ALT);
+ }
+ scintilla_refresh(sci);
+ }
+ // UTF-8 input.
+ //SSM(SCI_SETCODEPAGE, SC_CP_UTF8, 0);
+ //wint_t c = {0};
+ //WINDOW *win = scintilla_get_window(sci);
+ //while (c != 'q') {
+ // int status = wget_wch(win, &c);
+ // if (status == ERR)
+ // continue;
+ // else if (status == KEY_CODE_YES) {
+ // if (c == KEY_UP) c = SCK_UP;
+ // else if (c == KEY_DOWN) c = SCK_DOWN;
+ // else if (c == KEY_LEFT) c = SCK_LEFT;
+ // else if (c == KEY_RIGHT) c = SCK_RIGHT;
+ // }
+ // scintilla_send_key(sci, c, FALSE, FALSE, FALSE);
+ // scintilla_refresh(sci);
+ //}
+
+ printf("\033[?1000l"); // disable mouse press and release events
+ //printf("\033[?1002l"); // disable mouse press, drag, and release events
+ //printf("\033[?1003l"); // disable mouse move, press, drag, and release
+
+ scintilla_delete(sci);
+ endwin();
+
+ return 0;
+}
diff --git a/doc/ScintillaRelated.html b/doc/ScintillaRelated.html
index 0c8c9f91b..5013919f9 100644
--- a/doc/ScintillaRelated.html
+++ b/doc/ScintillaRelated.html
@@ -30,10 +30,6 @@
Ports and Bindings of Scintilla
</h3>
<p>
- <a href="http://foicica.com/scinterm/">Scinterm</a>
- is an implementation of Scintilla for the ncurses platform.
- </p>
- <p>
<a href="http://www.morphos-team.net/releasenotes/3.0">Scintilla.mcc</a>
is a port to MorphOS.
</p>
diff --git a/scripts/HeaderCheck.py b/scripts/HeaderCheck.py
index e36ffa18e..fd644f7db 100644
--- a/scripts/HeaderCheck.py
+++ b/scripts/HeaderCheck.py
@@ -45,6 +45,7 @@ def CheckFiles(root):
# The Qt platform code interleaves system and Scintilla headers
#~ filePaths += glob.glob(root + "/qt/ScintillaEditBase/*.cpp")
#~ filePaths += glob.glob(root + "/qt/ScintillaEdit/*.cpp")
+ filePaths += glob.glob(root + "/curses/*.cxx")
#~ print(filePaths)
masterHeaderList = ExtractHeaders(root + "/scripts/HeaderOrder.txt")
for f in filePaths:
diff --git a/scripts/HeaderOrder.txt b/scripts/HeaderOrder.txt
index af3891ba9..699963aa9 100644
--- a/scripts/HeaderOrder.txt
+++ b/scripts/HeaderOrder.txt
@@ -15,6 +15,7 @@
#include <ctype.h>
#include <limits.h>
#include <sys/time.h>
+#include <wchar.h>
// C++ wrappers of C standard library
#include <cstddef>
@@ -164,6 +165,9 @@
#import "ScintillaCocoa.h"
#import "PlatCocoa.h"
+// curses
+#include "ScintillaCurses.h"
+
// Catch testing framework
#include "catch.hpp"