summary refs log tree commit diff stats
path: root/lib
diff options
context:
space:
mode:
authorAndreas Rumpf <rumpf_a@web.de>2020-07-20 14:39:27 +0200
committerGitHub <noreply@github.com>2020-07-20 14:39:27 +0200
commit70acba7f0d4d73bce941dbd067a00255426bc697 (patch)
treef4a7c16b731c88974e04be7e1dbe756a7eb3046d /lib
parent49d4b50fe52f0e961ef367a44be8b1bd79994a03 (diff)
parent41c97e4b70d8d3b67f9080939790c6b9a64a7e74 (diff)
downloadNim-70acba7f0d4d73bce941dbd067a00255426bc697.tar.gz
readLine: Unicode support for Windows console (#14782)
* readLine: Unicode support for Windows console

When input is read from the Windows console, input encoding is UTF16. This is translated internally to UTF8.

* readLine: Remove recursive imports

* readLine: Fix issues with --gc:arc

**--gc:arc** defines **nimv2**. This changes the definition of **WideCStringObj**.
Also an empty string should be returned in case of EOF.
Diffstat (limited to 'lib')
-rw-r--r--lib/system/io.nim62
1 files changed, 62 insertions, 0 deletions
diff --git a/lib/system/io.nim b/lib/system/io.nim
index 0b624de8f..3d5cf981a 100644
--- a/lib/system/io.nim
+++ b/lib/system/io.nim
@@ -357,6 +357,68 @@ proc readLine*(f: File, line: var TaintedString): bool {.tags: [ReadIOEffect],
   proc c_memchr(s: pointer, c: cint, n: csize_t): pointer {.
     importc: "memchr", header: "<string.h>".}
 
+  when defined(windows) and not defined(useWinAnsi):
+    proc readConsole(hConsoleInput: FileHandle, lpBuffer: pointer,
+                     nNumberOfCharsToRead: int32,
+                     lpNumberOfCharsRead: ptr int32,
+                     pInputControl: pointer): int32 {.
+      importc: "ReadConsoleW", stdcall, dynlib: "kernel32".}
+
+    proc getLastError(): int32 {.
+      importc: "GetLastError", stdcall, dynlib: "kernel32", sideEffect.}
+
+    proc formatMessageW(dwFlags: int32, lpSource: pointer,
+                        dwMessageId, dwLanguageId: int32,
+                        lpBuffer: pointer, nSize: int32,
+                        arguments: pointer): int32 {.
+      importc: "FormatMessageW", stdcall, dynlib: "kernel32".}
+
+    proc localFree(p: pointer) {.
+      importc: "LocalFree", stdcall, dynlib: "kernel32".}
+
+    proc isatty(f: File): bool =
+      when defined(posix):
+        proc isatty(fildes: FileHandle): cint {.
+          importc: "isatty", header: "<unistd.h>".}
+      else:
+        proc isatty(fildes: FileHandle): cint {.
+          importc: "_isatty", header: "<io.h>".}
+      result = isatty(getFileHandle(f)) != 0'i32
+
+    # this implies the file is open
+    if f.isatty:
+      const numberOfCharsToRead = 2048
+      var numberOfCharsRead = 0'i32
+      var buffer = newWideCString("", numberOfCharsToRead)
+      if readConsole(getOsFileHandle(f), addr(buffer[0]),
+        numberOfCharsToRead, addr(numberOfCharsRead), nil) == 0:
+        var error = getLastError()
+        var errorMsg: string
+        var msgbuf: WideCString
+        if formatMessageW(0x00000100 or 0x00001000 or 0x00000200,
+                        nil, error, 0, addr(msgbuf), 0, nil) != 0'i32:
+          errorMsg = $msgbuf
+          if msgbuf != nil:
+            localFree(cast[pointer](msgbuf))
+        raiseEIO("error: " & $error & " `" & errorMsg & "`")
+      # input always ends with "\r\n"
+      numberOfCharsRead -= 2
+      # handle Ctrl+Z as EOF
+      for i in 0..<numberOfCharsRead:
+        if buffer[i].uint16 == 26:  #Ctrl+Z
+          close(f)  #has the same effect as setting EOF
+          if i == 0:
+            line = TaintedString("")
+            return false
+          numberOfCharsRead = i
+          break
+      buffer[numberOfCharsRead] = 0.Utf16Char
+      when defined(nimv2):
+        line = TaintedString($toWideCString(buffer))
+      else:
+        line = TaintedString($buffer)
+      return(true)
+
   var pos = 0
 
   # Use the currently reserved space for a first try