Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: isTty #3930

Merged
merged 2 commits into from
Apr 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 20 additions & 14 deletions src/Init/System/IO.lean
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,8 @@ Note that EOF does not actually close a stream, so further reads may block and r
-/
getLine : IO String
putStr : String → IO Unit
/-- Returns true if a stream refers to a Windows console or Unix terminal. -/
isTty : BaseIO Bool
deriving Inhabited

open FS
Expand Down Expand Up @@ -360,6 +362,9 @@ Will succeed even if no lock has been acquired.
-/
@[extern "lean_io_prim_handle_unlock"] opaque unlock (h : @& Handle) : IO Unit

/-- Returns true if a handle refers to a Windows console or Unix terminal. -/
@[extern "lean_io_prim_handle_is_tty"] opaque isTty (h : @& Handle) : BaseIO Bool

@[extern "lean_io_prim_handle_flush"] opaque flush (h : @& Handle) : IO Unit
/-- Rewinds the read/write cursor to the beginning of the handle. -/
@[extern "lean_io_prim_handle_rewind"] opaque rewind (h : @& Handle) : IO Unit
Expand Down Expand Up @@ -743,36 +748,37 @@ namespace FS
namespace Stream

@[export lean_stream_of_handle]
def ofHandle (h : Handle) : Stream := {
flush := Handle.flush h,
read := Handle.read h,
write := Handle.write h,
getLine := Handle.getLine h,
putStr := Handle.putStr h,
}
def ofHandle (h : Handle) : Stream where
flush := Handle.flush h
read := Handle.read h
write := Handle.write h
getLine := Handle.getLine h
putStr := Handle.putStr h
isTty := Handle.isTty h

structure Buffer where
data : ByteArray := ByteArray.empty
pos : Nat := 0

def ofBuffer (r : Ref Buffer) : Stream := {
flush := pure (),
def ofBuffer (r : Ref Buffer) : Stream where
flush := pure ()
read := fun n => r.modifyGet fun b =>
let data := b.data.extract b.pos (b.pos + n.toNat)
(data, { b with pos := b.pos + data.size }),
(data, { b with pos := b.pos + data.size })
write := fun data => r.modify fun b =>
-- set `exact` to `false` so that repeatedly writing to the stream does not impose quadratic run time
{ b with data := data.copySlice 0 b.data b.pos data.size false, pos := b.pos + data.size },
{ b with data := data.copySlice 0 b.data b.pos data.size false, pos := b.pos + data.size }
getLine := r.modifyGet fun b =>
let pos := match b.data.findIdx? (start := b.pos) fun u => u == 0 || u = '\n'.toNat.toUInt8 with
-- include '\n', but not '\0'
| some pos => if b.data.get! pos == 0 then pos else pos + 1
| none => b.data.size
(String.fromUTF8Unchecked <| b.data.extract b.pos pos, { b with pos := pos }),
(String.fromUTF8Unchecked <| b.data.extract b.pos pos, { b with pos := pos })
putStr := fun s => r.modify fun b =>
let data := s.toUTF8
{ b with data := data.copySlice 0 b.data b.pos data.size false, pos := b.pos + data.size },
}
{ b with data := data.copySlice 0 b.data b.pos data.size false, pos := b.pos + data.size }
isTty := pure false

end Stream

/-- Run action with `stdin` emptied and `stdout+stderr` captured into a `String`. -/
Expand Down
34 changes: 29 additions & 5 deletions src/runtime/io.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -302,11 +302,7 @@ extern "C" LEAN_EXPORT obj_res lean_io_prim_handle_mk(b_obj_arg filename, uint8
#ifdef LEAN_WINDOWS

static inline HANDLE win_handle(FILE * fp) {
#ifdef q4_WCE
return (HANDLE)_fileno(fp);
#else
return (HANDLE)_get_osfhandle(_fileno(fp));
#endif
}

/* Handle.lock : (@& Handle) → (exclusive : Bool) → IO Unit */
Expand Down Expand Up @@ -391,13 +387,41 @@ extern "C" LEAN_EXPORT obj_res lean_io_prim_handle_unlock(b_obj_arg h, obj_arg /

#endif

/* Handle.isTty : (@& Handle) → BaseIO Bool */
extern "C" LEAN_EXPORT obj_res lean_io_prim_handle_is_tty(b_obj_arg h, obj_arg /* w */) {
FILE * fp = io_get_handle(h);
#ifdef LEAN_WINDOWS
/*
On Windows, there are two approaches for detecting a console.
1) _isatty(_fileno(fp)) != 0
This checks whether the file descriptor is a *character device*,
not just a terminal (unlike Unix's isatty). Thus, it produces a false
positive in some edge cases (such as NUL).
https://stackoverflow.com/q/3648711
2) GetConsoleMode(win_handle(fp), &mode) != 0
Errors if the handle is not a console. Unfortunately, this produces
a false negative for a terminal emulator like MSYS/Cygwin's Mintty,
which is not implemented as a Windows-recognized console on
old Windows versions (e.g., pre-Windows 10, pre-ConPTY).
tydeu marked this conversation as resolved.
Show resolved Hide resolved
https://github.com/msys2/MINGW-packages/issues/14087
We choose to use GetConsoleMode as that seems like the more modern approach,
and Lean does not support pre-Windows 10.
*/
DWORD mode;
return io_result_mk_ok(box(GetConsoleMode(win_handle(fp), &mode) != 0));
#else
// We ignore errors for consistency with Windows.
return io_result_mk_ok(box(isatty(fileno(fp))));
#endif
}

/* Handle.isEof : (@& Handle) → BaseIO Bool */
extern "C" LEAN_EXPORT obj_res lean_io_prim_handle_is_eof(b_obj_arg h, obj_arg /* w */) {
FILE * fp = io_get_handle(h);
return io_result_mk_ok(box(std::feof(fp) != 0));
}

/* Handle.flush : (@& Handle) → IO Bool */
/* Handle.flush : (@& Handle) → IO Unit */
extern "C" LEAN_EXPORT obj_res lean_io_prim_handle_flush(b_obj_arg h, obj_arg /* w */) {
FILE * fp = io_get_handle(h);
if (!std::fflush(fp)) {
Expand Down
Loading