]> git.lizzy.rs Git - linenoise.git/blobdiff - linenoise-win32.c
Implement multiline and hints support
[linenoise.git] / linenoise-win32.c
diff --git a/linenoise-win32.c b/linenoise-win32.c
new file mode 100644 (file)
index 0000000..dd15b94
--- /dev/null
@@ -0,0 +1,375 @@
+
+/* this code is not standalone
+ * it is included into linenoise.c
+ * for windows.
+ * It is deliberately kept separate so that
+ * applications that have no need for windows
+ * support can omit this
+ */
+static DWORD orig_consolemode = 0;
+
+static int flushOutput(struct current *current);
+static void outputNewline(struct current *current);
+
+static void refreshStart(struct current *current)
+{
+}
+
+static void refreshEnd(struct current *current)
+{
+}
+
+static void refreshStartChars(struct current *current)
+{
+    assert(current->output == NULL);
+    /* We accumulate all output here */
+    current->output = sb_alloc();
+#ifdef USE_UTF8
+    current->ubuflen = 0;
+#endif
+}
+
+static void refreshNewline(struct current *current)
+{
+    DRL("<nl>");
+    outputNewline(current);
+}
+
+static void refreshEndChars(struct current *current)
+{
+    assert(current->output);
+    flushOutput(current);
+    sb_free(current->output);
+    current->output = NULL;
+}
+
+static int enableRawMode(struct current *current) {
+    DWORD n;
+    INPUT_RECORD irec;
+
+    current->outh = GetStdHandle(STD_OUTPUT_HANDLE);
+    current->inh = GetStdHandle(STD_INPUT_HANDLE);
+
+    if (!PeekConsoleInput(current->inh, &irec, 1, &n)) {
+        return -1;
+    }
+    if (getWindowSize(current) != 0) {
+        return -1;
+    }
+    if (GetConsoleMode(current->inh, &orig_consolemode)) {
+        SetConsoleMode(current->inh, ENABLE_PROCESSED_INPUT);
+    }
+#ifdef USE_UTF8
+    /* XXX is this the right thing to do? */
+    SetConsoleCP(65001);
+#endif
+    return 0;
+}
+
+static void disableRawMode(struct current *current)
+{
+    SetConsoleMode(current->inh, orig_consolemode);
+}
+
+void linenoiseClearScreen(void)
+{
+    /* XXX: This is ugly. Should just have the caller pass a handle */
+    struct current current;
+
+    current.outh = GetStdHandle(STD_OUTPUT_HANDLE);
+
+    if (getWindowSize(&current) == 0) {
+        COORD topleft = { 0, 0 };
+        DWORD n;
+
+        FillConsoleOutputCharacter(current.outh, ' ',
+            current.cols * current.rows, topleft, &n);
+        FillConsoleOutputAttribute(current.outh,
+            FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_GREEN,
+            current.cols * current.rows, topleft, &n);
+        SetConsoleCursorPosition(current.outh, topleft);
+    }
+}
+
+static void cursorToLeft(struct current *current)
+{
+    COORD pos;
+    DWORD n;
+
+    pos.X = 0;
+    pos.Y = (SHORT)current->y;
+
+    FillConsoleOutputAttribute(current->outh,
+        FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_GREEN, current->cols, pos, &n);
+    current->x = 0;
+}
+
+#ifdef USE_UTF8
+static void flush_ubuf(struct current *current)
+{
+    COORD pos;
+    DWORD nwritten;
+    pos.Y = (SHORT)current->y;
+    pos.X = (SHORT)current->x;
+    SetConsoleCursorPosition(current->outh, pos);
+    WriteConsoleW(current->outh, current->ubuf, current->ubuflen, &nwritten, 0);
+    current->x += current->ubufcols;
+    current->ubuflen = 0;
+    current->ubufcols = 0;
+}
+
+static void add_ubuf(struct current *current, int ch)
+{
+    /* This code originally by: Author: Mark E. Davis, 1994. */
+    static const int halfShift  = 10; /* used for shifting by 10 bits */
+
+    static const DWORD halfBase = 0x0010000UL;
+    static const DWORD halfMask = 0x3FFUL;
+
+    #define UNI_SUR_HIGH_START  0xD800
+    #define UNI_SUR_HIGH_END    0xDBFF
+    #define UNI_SUR_LOW_START   0xDC00
+    #define UNI_SUR_LOW_END     0xDFFF
+
+    #define UNI_MAX_BMP 0x0000FFFF
+
+    if (ch > UNI_MAX_BMP) {
+        /* convert from unicode to utf16 surrogate pairs
+         * There is always space for one extra word in ubuf
+         */
+        ch -= halfBase;
+        current->ubuf[current->ubuflen++] = (WORD)((ch >> halfShift) + UNI_SUR_HIGH_START);
+        current->ubuf[current->ubuflen++] = (WORD)((ch & halfMask) + UNI_SUR_LOW_START);
+    }
+    else {
+        current->ubuf[current->ubuflen++] = ch;
+    }
+    current->ubufcols += utf8_width(ch);
+    if (current->ubuflen >= UBUF_MAX_CHARS) {
+        flush_ubuf(current);
+    }
+}
+#endif
+
+static int flushOutput(struct current *current)
+{
+    const char *pt = sb_str(current->output);
+    int len = sb_len(current->output);
+
+#ifdef USE_UTF8
+    /* convert utf8 in current->output into utf16 in current->ubuf
+     */
+    while (len) {
+        int ch;
+        int n = utf8_tounicode(pt, &ch);
+
+        pt += n;
+        len -= n;
+
+        add_ubuf(current, ch);
+    }
+    flush_ubuf(current);
+#else
+    DWORD nwritten;
+    COORD pos;
+
+    pos.Y = (SHORT)current->y;
+    pos.X = (SHORT)current->x;
+
+    SetConsoleCursorPosition(current->outh, pos);
+    WriteConsoleA(current->outh, pt, len, &nwritten, 0);
+
+    current->x += len;
+#endif
+
+    sb_clear(current->output);
+
+    return 0;
+}
+
+static int outputChars(struct current *current, const char *buf, int len)
+{
+    if (len < 0) {
+        len = strlen(buf);
+    }
+    assert(current->output);
+
+    sb_append_len(current->output, buf, len);
+
+    return 0;
+}
+
+static void outputNewline(struct current *current)
+{
+    /* On the last row output a newline to force a scroll */
+    if (current->y + 1 == current->rows) {
+        outputChars(current, "\n", 1);
+    }
+    flushOutput(current);
+    current->x = 0;
+    current->y++;
+}
+
+static void setOutputHighlight(struct current *current, const int *props, int nprops)
+{
+    int colour = FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_GREEN;
+    int bold = 0;
+    int reverse = 0;
+    int i;
+
+    for (i = 0; i < nprops; i++) {
+        switch (props[i]) {
+            case 0:
+               colour = FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_GREEN;
+               bold = 0;
+               reverse = 0;
+               break;
+            case 1:
+               bold = FOREGROUND_INTENSITY;
+               break;
+            case 7:
+               reverse = 1;
+               break;
+            case 30:
+               colour = 0;
+               break;
+            case 31:
+               colour = FOREGROUND_RED;
+               break;
+            case 32:
+               colour = FOREGROUND_GREEN;
+               break;
+            case 33:
+               colour = FOREGROUND_RED | FOREGROUND_GREEN;
+               break;
+            case 34:
+               colour = FOREGROUND_BLUE;
+               break;
+            case 35:
+               colour = FOREGROUND_RED | FOREGROUND_BLUE;
+               break;
+            case 36:
+               colour = FOREGROUND_BLUE | FOREGROUND_GREEN;
+               break;
+            case 37:
+               colour = FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_GREEN;
+               break;
+        }
+    }
+
+    flushOutput(current);
+
+    if (reverse) {
+        SetConsoleTextAttribute(current->outh, BACKGROUND_INTENSITY);
+    }
+    else {
+        SetConsoleTextAttribute(current->outh, colour | bold);
+    }
+}
+
+static void eraseEol(struct current *current)
+{
+    COORD pos;
+    DWORD n;
+
+    pos.X = (SHORT) current->x;
+    pos.Y = (SHORT) current->y;
+
+    FillConsoleOutputCharacter(current->outh, ' ', current->cols - current->x, pos, &n);
+}
+
+static void setCursorXY(struct current *current)
+{
+    COORD pos;
+
+    pos.X = (SHORT) current->x;
+    pos.Y = (SHORT) current->y;
+
+    SetConsoleCursorPosition(current->outh, pos);
+}
+
+
+static void setCursorPos(struct current *current, int x)
+{
+    current->x = x;
+    setCursorXY(current);
+}
+
+static void cursorUp(struct current *current, int n)
+{
+    current->y -= n;
+    setCursorXY(current);
+}
+
+static void cursorDown(struct current *current, int n)
+{
+    current->y += n;
+    setCursorXY(current);
+}
+
+static int fd_read(struct current *current)
+{
+    while (1) {
+        INPUT_RECORD irec;
+        DWORD n;
+        if (WaitForSingleObject(current->inh, INFINITE) != WAIT_OBJECT_0) {
+            break;
+        }
+        if (!ReadConsoleInputW(current->inh, &irec, 1, &n)) {
+            break;
+        }
+        if (irec.EventType == KEY_EVENT) {
+            KEY_EVENT_RECORD *k = &irec.Event.KeyEvent;
+            if (k->bKeyDown || k->wVirtualKeyCode == VK_MENU) {
+                if (k->dwControlKeyState & ENHANCED_KEY) {
+                    switch (k->wVirtualKeyCode) {
+                     case VK_LEFT:
+                        return SPECIAL_LEFT;
+                     case VK_RIGHT:
+                        return SPECIAL_RIGHT;
+                     case VK_UP:
+                        return SPECIAL_UP;
+                     case VK_DOWN:
+                        return SPECIAL_DOWN;
+                     case VK_INSERT:
+                        return SPECIAL_INSERT;
+                     case VK_DELETE:
+                        return SPECIAL_DELETE;
+                     case VK_HOME:
+                        return SPECIAL_HOME;
+                     case VK_END:
+                        return SPECIAL_END;
+                     case VK_PRIOR:
+                        return SPECIAL_PAGE_UP;
+                     case VK_NEXT:
+                        return SPECIAL_PAGE_DOWN;
+                    }
+                }
+                /* Note that control characters are already translated in AsciiChar */
+                else if (k->wVirtualKeyCode == VK_CONTROL)
+                    continue;
+                else {
+                    return k->uChar.UnicodeChar;
+                }
+            }
+        }
+    }
+    return -1;
+}
+
+static int getWindowSize(struct current *current)
+{
+    CONSOLE_SCREEN_BUFFER_INFO info;
+    if (!GetConsoleScreenBufferInfo(current->outh, &info)) {
+        return -1;
+    }
+    current->cols = info.dwSize.X;
+    current->rows = info.dwSize.Y;
+    if (current->cols <= 0 || current->rows <= 0) {
+        current->cols = 80;
+        return -1;
+    }
+    current->y = info.dwCursorPosition.Y;
+    current->x = info.dwCursorPosition.X;
+    return 0;
+}