├── .gitmodules ├── t ├── 00-load.rakutest └── 01-history.rakutest ├── .gitignore ├── Changes ├── .github └── workflows │ ├── macos.yml │ ├── test.yml │ └── windows.yml ├── dist.ini ├── Build.rakumod ├── META6.json ├── Makefile.in ├── fill-constants.pl ├── constant-helper.c ├── LICENSE ├── README.md ├── linenoise.h ├── lib └── Linenoise.rakumod.in └── linenoise.c /.gitmodules: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /t/00-load.rakutest: -------------------------------------------------------------------------------- 1 | use Test; 2 | 3 | plan 1; 4 | 5 | use-ok "Linenoise", "Can use module"; 6 | 7 | # vim: expandtab shiftwidth=4 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /Makefile 2 | /linenoise.o 3 | /constant-helper 4 | /resources/ 5 | .precomp 6 | /lib/.precomp 7 | /lib/Linenoise.rakumod 8 | *~ 9 | -------------------------------------------------------------------------------- /Changes: -------------------------------------------------------------------------------- 1 | Revision history for Linenoise 2 | 3 | {{$NEXT}} 4 | 5 | 0.1.2 2022-03-17T20:50:34+01:00 6 | - First version in the zef ecosystem 7 | -------------------------------------------------------------------------------- /.github/workflows/macos.yml: -------------------------------------------------------------------------------- 1 | name: MacOS test 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | test: 6 | runs-on: macos-latest 7 | steps: 8 | - name: Checkout 9 | uses: actions/checkout@v1 10 | 11 | - name: Build 12 | run: | 13 | brew install rakudo-star 14 | zef build . 15 | zef test . 16 | -------------------------------------------------------------------------------- /t/01-history.rakutest: -------------------------------------------------------------------------------- 1 | use Test; 2 | 3 | plan 1; 4 | 5 | use Linenoise; 6 | 7 | my $history-str = "say 33"; 8 | linenoiseHistoryAdd($history-str); 9 | my $history-path = "$*TMPDIR/linenoise-history.txt"; 10 | linenoiseHistorySave($history-path); 11 | is($history-path.IO.slurp.chomp,$history-str, "History saves OK"); 12 | 13 | # vim: expandtab shiftwidth=4 14 | -------------------------------------------------------------------------------- /dist.ini: -------------------------------------------------------------------------------- 1 | name = Linenoise 2 | 3 | [ReadmeFromPod] 4 | enabled = false 5 | filename = lib/Linenoise.rakumod.in 6 | 7 | [UploadToZef] 8 | 9 | [PruneFiles] 10 | ; match = ^ 'xt/' 11 | 12 | [Badges] 13 | provider = github-actions/test.yml 14 | provider = github-actions/macos.yml 15 | provider = github-actions/windows.yml 16 | 17 | [AutoScanPackages] 18 | enabled = false 19 | -------------------------------------------------------------------------------- /Build.rakumod: -------------------------------------------------------------------------------- 1 | use LibraryMake; 2 | 3 | class Build { 4 | method build($srcdir) { 5 | my %vars = get-vars($srcdir); 6 | %vars = $*VM.platform-library-name('linenoise'.IO).basename; 7 | mkdir IO::Path.new("$srcdir/resources/libraries") unless "$srcdir/resources/libraries".IO.e; 8 | say "Processing makefile with %vars"; 9 | process-makefile($srcdir, %vars); 10 | shell %vars, :cwd($srcdir); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /META6.json: -------------------------------------------------------------------------------- 1 | { 2 | "auth": "zef:raku-community-modules", 3 | "author": "Rob Hoelz", 4 | "build-depends": [ 5 | "LibraryMake" 6 | ], 7 | "depends": [ 8 | ], 9 | "description": "Raku bindings to linenoise", 10 | "license": "MIT", 11 | "name": "Linenoise", 12 | "perl": "6.*", 13 | "provides": { 14 | "Linenoise": "lib/Linenoise.rakumod" 15 | }, 16 | "resources": [ 17 | "libraries/linenoise" 18 | ], 19 | "source-url": "https://github.com/raku-community-modules/Linenoise.git", 20 | "tags": [ 21 | ], 22 | "test-depends": [ 23 | ], 24 | "version": "0.1.2" 25 | } 26 | -------------------------------------------------------------------------------- /Makefile.in: -------------------------------------------------------------------------------- 1 | name = %linenoise% 2 | 3 | all: %DESTDIR%/resources/libraries/%linenoise% lib/Linenoise.rakumod 4 | clean: 5 | rm %DESTDIR%/resources/libraries/%linenoise% 6 | 7 | %DESTDIR%/resources/libraries/%linenoise%: linenoise%O% 8 | raku -e "mkdir 'resources/libraries'" 9 | %LD% %LDSHARED% %LDFLAGS% %LIBS% %LDOUT%resources/libraries/%linenoise% linenoise%O% 10 | 11 | linenoise%O%: linenoise.c 12 | echo Compiling with %CC% %0% 13 | %CC% -c %CCSHARED% %CCFLAGS% %CCOUT% linenoise%O% linenoise.c 14 | 15 | constant-helper: constant-helper.c 16 | %CC% %CCOUT%constant-helper %CCFLAGS% constant-helper.c 17 | 18 | lib/Linenoise.rakumod: lib/Linenoise.rakumod.in constant-helper 19 | raku fill-constants.pl < lib/Linenoise.rakumod.in > lib/Linenoise.rakumod 20 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | push: 5 | branches: 6 | - '*' 7 | tags-ignore: 8 | - '*' 9 | pull_request: 10 | 11 | jobs: 12 | raku: 13 | strategy: 14 | matrix: 15 | os: 16 | - ubuntu-latest 17 | - macos-latest 18 | raku-version: 19 | - 'latest' 20 | runs-on: ${{ matrix.os }} 21 | steps: 22 | - uses: actions/checkout@v2 23 | - uses: Raku/setup-raku@v1 24 | with: 25 | raku-version: ${{ matrix.raku-version }} 26 | - name: Install Dependencies 27 | run: zef install --/test --test-depends --deps-only . 28 | - name: Install App::Prove6 29 | run: zef install --/test App::Prove6 30 | - name: Run Tests 31 | run: zef build . && zef test . 32 | -------------------------------------------------------------------------------- /fill-constants.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl6 2 | 3 | use v6; 4 | 5 | sub get-constants(@lines) { 6 | gather for @lines -> $definition { 7 | my ( $key, $value ) = $definition.split('='); 8 | take $key, val($value); 9 | } 10 | } 11 | 12 | sub MAIN { 13 | my %constants = get-constants(run('./constant-helper', :out).out.lines()).flat; 14 | 15 | for lines() -> $line { 16 | my $new-line = $line; 17 | if $line ~~ /my \s+ constant \s+ $=[\w+] \s* '=' \s* $=['#`(FILL-ME-IN)']/ { 18 | my $ident = ~$; 19 | 20 | if %constants{$ident} ~~ Str { 21 | $new-line.substr-rw($.from, $.chars) = %constants{$ident}; 22 | } else { 23 | $new-line.substr-rw($.from, $.chars) = '0x%x'.sprintf(%constants{$ident}); 24 | } 25 | } 26 | say $new-line; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /constant-helper.c: -------------------------------------------------------------------------------- 1 | #if _WIN32 || _WIN64 2 | # define F_GETFL 0 3 | # define F_SETFL 0 4 | # define O_NONBLOCK 0 5 | #else 6 | # include 7 | #endif 8 | 9 | #include 10 | #include 11 | 12 | static int 13 | msc_version_to_dll_version(int msc_version) 14 | { 15 | if(msc_version == 800) { 16 | return 10; /* 1.0 is an exception */ 17 | } else { 18 | return (msc_version / 10) - 60; 19 | } 20 | } 21 | 22 | int 23 | main(void) 24 | { 25 | printf("F_GETFL=%d\n", F_GETFL); 26 | printf("F_SETFL=%d\n", F_SETFL); 27 | printf("O_NONBLOCK=%d\n", O_NONBLOCK); 28 | 29 | #if _WIN32 || _WIN64 30 | # if defined(_MSC_VER) && (_MSC_VER < 1000 || _MSC_VER >= 1300) 31 | printf("CLIB=\"msvcr%d.dll\"\n", msc_version_to_dll_version(_MSC_VER)); 32 | # else 33 | printf("CLIB=\"msvcrt.dll\"\n"); 34 | # endif 35 | #else 36 | printf("CLIB=Str\n"); 37 | #endif 38 | 39 | return 0; 40 | } 41 | -------------------------------------------------------------------------------- /.github/workflows/windows.yml: -------------------------------------------------------------------------------- 1 | name: Windows test 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | test: 6 | runs-on: windows-latest 7 | steps: 8 | - name: Checkout 9 | uses: actions/checkout@v1 10 | 11 | - name: Build 12 | run: | 13 | cinst rakudostar 14 | $env:Path = "C:\rakudo\bin;C:\rakudo\share\perl6\site\bin;$env:Path" 15 | refreshenv 16 | gcc -c -fPIC -Werror=declaration-after-statement -Werror=pointer-arith -O3 -DNDEBUG -D_REENTRANT -D_FILE_OFFSET_BITS=64 -fPIC -o linenoise.obj linenoise.c 17 | gcc -o constant-helper.exe -Werror=declaration-after-statement -Werror=pointer-arith -O3 -DNDEBUG -D_REENTRANT -D_FILE_OFFSET_BITS=64 -fPIC constant-helper.c 18 | raku -e "mkdir 'resources/libraries'" 19 | gcc -shared -o resources/libraries/linenoise.dll linenoise.obj 20 | Get-Content lib/Linenoise.rakumod.in | perl6 fill-constants.pl > lib/Linenoise.rakumod 21 | zef test . --verbose 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Rob Hoelz 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Actions Status](https://github.com/raku-community-modules/Linenoise/workflows/test/badge.svg)](https://github.com/raku-community-modules/Linenoise/actions) 2 | [![Actions Status](https://github.com/raku-community-modules/Linenoise/workflows/MacOS%20test/badge.svg)](https://github.com/raku-community-modules/Linenoise/actions) 3 | [![Actions Status](https://github.com/raku-community-modules/Linenoise/workflows/Windows%20test/badge.svg)](https://github.com/raku-community-modules/Linenoise/actions) 4 | 5 | # Name 6 | 7 | Linenoise 8 | 9 | # Author 10 | 11 | Rob Hoelz 12 | 13 | # Synopsis 14 | 15 | ```raku 16 | use Linenoise; 17 | 18 | while (my $line = linenoise '> ').defined { 19 | say "got a line: $line"; 20 | } 21 | ``` 22 | 23 | # Description 24 | 25 | This module provides bindings to linenoise ([https://github.com/antirez/linenoise](https://github.com/antirez/linenoise)) for Raku via NativeCall. 26 | 27 | # Installation 28 | 29 | You can install via zef: 30 | 31 | ``` 32 | $ zef install Linenoise 33 | ``` 34 | 35 | Note that since this module has binary components, you'll need a working C compiler. Everything you need can be found under the `build-essential` package 36 | on Debian-based Linux distributions, such as Ubuntu. 37 | 38 | # Examples 39 | 40 | ## Basic History 41 | 42 | ```raku 43 | use Linenoise; 44 | 45 | my constant HIST_FILE = '.myhist'; 46 | my constant HIST_LEN = 10; 47 | 48 | linenoiseHistoryLoad(HIST_FILE); 49 | linenoiseHistorySetMaxLen(HIST_LEN); 50 | 51 | while (my $line = linenoise '> ').defined { 52 | linenoiseHistoryAdd($line); 53 | say "got a line: $line"; 54 | } 55 | 56 | linenoiseHistorySave(HIST_FILE); 57 | ``` 58 | 59 | ## Tab Completion 60 | 61 | ```raku 62 | use Linenoise; 63 | 64 | my @commands = ; 65 | 66 | linenoiseSetCompletionCallback(-> $line, $c { 67 | my ( $prefix, $last-word ) = find-last-word($line); 68 | 69 | for @commands.grep(/^ "$last-word" /).sort -> $cmd { 70 | linenoiseAddCompletion($c, $prefix ~ $cmd); 71 | } 72 | }); 73 | 74 | while (my $line = linenoise '> ').defined { 75 | say "got a line: $line"; 76 | } 77 | ``` 78 | 79 | COPYRIGHT AND LICENSE 80 | ===================== 81 | 82 | Copyright 2015 - 2017 Rob Hoelz 83 | 84 | Copyright 2018 - 2022 Raku Community 85 | 86 | This library is free software; you can redistribute it and/or modify it under the MIT license. 87 | -------------------------------------------------------------------------------- /linenoise.h: -------------------------------------------------------------------------------- 1 | /* linenoise.h -- VERSION 1.0 2 | * 3 | * Guerrilla line editing library against the idea that a line editing lib 4 | * needs to be 20,000 lines of C code. 5 | * 6 | * See linenoise.c for more information. 7 | * 8 | * ------------------------------------------------------------------------ 9 | * 10 | * Copyright (c) 2010-2014, Salvatore Sanfilippo 11 | * Copyright (c) 2010-2013, Pieter Noordhuis 12 | * 13 | * All rights reserved. 14 | * 15 | * Redistribution and use in source and binary forms, with or without 16 | * modification, are permitted provided that the following conditions are 17 | * met: 18 | * 19 | * * Redistributions of source code must retain the above copyright 20 | * notice, this list of conditions and the following disclaimer. 21 | * 22 | * * Redistributions in binary form must reproduce the above copyright 23 | * notice, this list of conditions and the following disclaimer in the 24 | * documentation and/or other materials provided with the distribution. 25 | * 26 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 27 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 28 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 29 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 30 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 31 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 32 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 33 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 34 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 35 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 36 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 37 | */ 38 | 39 | #ifndef __LINENOISE_H 40 | #define __LINENOISE_H 41 | 42 | #ifdef __cplusplus 43 | extern "C" { 44 | #endif 45 | 46 | typedef struct linenoiseCompletions { 47 | size_t len; 48 | char **cvec; 49 | } linenoiseCompletions; 50 | 51 | typedef void(linenoiseCompletionCallback)(const char *, linenoiseCompletions *); 52 | void linenoiseSetCompletionCallback(linenoiseCompletionCallback *); 53 | void linenoiseAddCompletion(linenoiseCompletions *, const char *); 54 | 55 | char *linenoise(const char *prompt); 56 | int linenoiseHistoryAdd(const char *line); 57 | int linenoiseHistorySetMaxLen(int len); 58 | int linenoiseHistorySave(const char *filename); 59 | int linenoiseHistoryLoad(const char *filename); 60 | void linenoiseClearScreen(void); 61 | void linenoiseSetMultiLine(int ml); 62 | void linenoisePrintKeyCodes(void); 63 | 64 | #ifdef __cplusplus 65 | } 66 | #endif 67 | 68 | #endif /* __LINENOISE_H */ 69 | -------------------------------------------------------------------------------- /lib/Linenoise.rakumod.in: -------------------------------------------------------------------------------- 1 | use NativeCall; 2 | 3 | #| This module provides bindings to linenoise 4 | #| (L) for Raku 5 | #| via NativeCall. 6 | module Linenoise:ver<0.1.2>:auth { 7 | my constant STDIN_FILENO = 0; 8 | my constant F_GETFL = #`(FILL-ME-IN); 9 | my constant F_SETFL = #`(FILL-ME-IN); 10 | my constant O_NONBLOCK = #`(FILL-ME-IN); 11 | my constant CLIB = #`(FILL-ME-IN); 12 | 13 | my constant LIBLINENOISE = %?RESOURCES; 14 | 15 | my sub fcntl(int32 $fd, int32 $cmd, int32 $arg) returns int32 is native(Str) { * } 16 | my sub free(Pointer $p) is native(CLIB) { * } 17 | 18 | #| Completions objects are opaque data structures provided by linenoise 19 | #| that contain the current list of completions for the completions 20 | #| request. See L<#linenoiseAddCompletion> for more details. 21 | our class Completions is repr('CPointer') {} 22 | 23 | my sub linenoise_raw(Str $prompt) returns Pointer[Str] is native(LIBLINENOISE) is symbol('linenoise') { * } 24 | 25 | #| Adds an entry to the current history list. C<$line> must be C<.defined>! 26 | our sub linenoiseHistoryAdd(Str $line) is native(LIBLINENOISE) is export { * } 27 | 28 | #| Sets the maximum length of the history list. Entries at the front of this list will be 29 | #| evicted. 30 | our sub linenoiseHistorySetMaxLen(int32 $len) returns int32 is native(LIBLINENOISE) is export { * } 31 | 32 | #| Saves the current history list to a file. 33 | our sub linenoiseHistorySave(Str $filename) returns int32 is native(LIBLINENOISE) is export { * } 34 | 35 | #| Loads a file and populates the history list from its contents. 36 | our sub linenoiseHistoryLoad(Str $filename) returns int32 is native(LIBLINENOISE) is export { * } 37 | 38 | #| Clears the screen. 39 | our sub linenoiseClearScreen() is native(LIBLINENOISE) is export { * } 40 | 41 | #| Enables/disables multi line history mode. 42 | our sub linenoiseSetMultiLine(int32 $ml) is native(LIBLINENOISE) is export { * } 43 | 44 | #| Puts linenoise into key code printing mode (used for debugging). 45 | our sub linenoisePrintKeyCodes() is native(LIBLINENOISE) is export { * } 46 | 47 | #| Sets up a completion callback, invoked when the user presses tab. The 48 | #| callback gets the current line, and a completions object. See 49 | #| L<#linenoiseAddCompletion> to see how to add completions from within 50 | #| a callback. 51 | our sub linenoiseSetCompletionCallback(&callback (Str, Completions)) is native(LIBLINENOISE) is export { * } 52 | 53 | #| Adds a completion to the current set of completions. The first 54 | #| parameter is the completions object (which is passed into the callback), 55 | #| and the second is the completion to be added, as a full line. 56 | #| Completions are offered in the order in which they are provided to this 57 | #| function, so keep that in mind if you want your users to have a sorted 58 | #| list of completions. 59 | our sub linenoiseAddCompletion(Completions $completions, Str $completion) is native(LIBLINENOISE) is export { * } 60 | 61 | #| Prompts the user for a line of input after displaying L<$prompt>, and 62 | #| returns that line. During this operation, standard input is set to 63 | #| blocking, and line editing functions provided by linenoise are available. 64 | our sub linenoise(Str $prompt) returns Str is export { 65 | unless $*DISTRO.is-win { 66 | my $flags = fcntl(STDIN_FILENO, F_GETFL, 0); 67 | 68 | if $flags == -1 { 69 | fail "fcntl(\$*IN, F_GETFL, 0) failed"; 70 | } 71 | 72 | KEEP fcntl(STDIN_FILENO, F_SETFL, $flags); 73 | 74 | my $status = fcntl(STDIN_FILENO, F_SETFL, $flags +& +^O_NONBLOCK); 75 | 76 | if $status == -1 { 77 | fail "fcntl(\$*IN, F_SETFL, ~O_NONBLOCK) failed"; 78 | } 79 | } 80 | 81 | my $p = linenoise_raw($prompt); 82 | 83 | if $p { 84 | my $line = $p.deref; 85 | free($p); 86 | $line; 87 | } else { 88 | Str 89 | } 90 | } 91 | } 92 | 93 | =begin pod 94 | 95 | =head1 NAME 96 | 97 | Linenoise 98 | 99 | =head1 SYNOPSIS 100 | 101 | =begin code :lang 102 | 103 | use Linenoise; 104 | 105 | while (my $line = linenoise '> ').defined { 106 | say "got a line: $line"; 107 | } 108 | 109 | =end code 110 | 111 | =head1 DESCRIPTION 112 | 113 | This module provides bindings to linenoise 114 | (L) for Raku via NativeCall. 115 | 116 | =head1 EXAMPLES 117 | 118 | =head2 Basic History 119 | 120 | =begin code :lang 121 | 122 | use Linenoise; 123 | 124 | my constant HIST_FILE = '.myhist'; 125 | my constant HIST_LEN = 10; 126 | 127 | linenoiseHistoryLoad(HIST_FILE); 128 | linenoiseHistorySetMaxLen(HIST_LEN); 129 | 130 | while (my $line = linenoise '> ').defined { 131 | linenoiseHistoryAdd($line); 132 | say "got a line: $line"; 133 | } 134 | 135 | linenoiseHistorySave(HIST_FILE); 136 | 137 | =end code 138 | 139 | =head2 Tab Completion 140 | 141 | =begin code :lang 142 | 143 | use Linenoise; 144 | 145 | my @commands = ; 146 | 147 | linenoiseSetCompletionCallback(-> $line, $c { 148 | my ( $prefix, $last-word ) = find-last-word($line); 149 | 150 | for @commands.grep(/^ "$last-word" /).sort -> $cmd { 151 | linenoiseAddCompletion($c, $prefix ~ $cmd); 152 | } 153 | }); 154 | 155 | while (my $line = linenoise '> ').defined { 156 | say "got a line: $line"; 157 | } 158 | 159 | =end code 160 | 161 | =head1 AUTHOR 162 | 163 | Rob Hoelz 164 | 165 | Source can be located at: https://github.com/raku-community-modules/Linenoise . 166 | Comments and Pull Requests are welcome. 167 | 168 | =head1 COPYRIGHT AND LICENSE 169 | 170 | Copyright 2015 - 2017 Rob Hoelz 171 | 172 | Copyright 2018 - 2022 Raku Community 173 | 174 | This library is free software; you can redistribute it and/or modify it under the MIT license. 175 | 176 | =end pod 177 | 178 | # vim: expandtab shiftwidth=4 179 | -------------------------------------------------------------------------------- /linenoise.c: -------------------------------------------------------------------------------- 1 | /* linenoise.c -- VERSION 1.0 2 | * 3 | * Guerrilla line editing library against the idea that a line editing lib 4 | * needs to be 20,000 lines of C code. 5 | * 6 | * You can find the latest source code at: 7 | * 8 | * http://github.com/antirez/linenoise 9 | * 10 | * Does a number of crazy assumptions that happen to be true in 99.9999% of 11 | * the 2010 UNIX computers around. 12 | * 13 | * ------------------------------------------------------------------------ 14 | * 15 | * Copyright (c) 2010-2014, Salvatore Sanfilippo 16 | * Copyright (c) 2010-2013, Pieter Noordhuis 17 | * 18 | * All rights reserved. 19 | * 20 | * Redistribution and use in source and binary forms, with or without 21 | * modification, are permitted provided that the following conditions are 22 | * met: 23 | * 24 | * * Redistributions of source code must retain the above copyright 25 | * notice, this list of conditions and the following disclaimer. 26 | * 27 | * * Redistributions in binary form must reproduce the above copyright 28 | * notice, this list of conditions and the following disclaimer in the 29 | * documentation and/or other materials provided with the distribution. 30 | * 31 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 32 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 33 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 34 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 35 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 36 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 37 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 38 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 39 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 40 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 41 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 42 | * 43 | * ------------------------------------------------------------------------ 44 | * 45 | * References: 46 | * - http://invisible-island.net/xterm/ctlseqs/ctlseqs.html 47 | * - http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html 48 | * 49 | * Todo list: 50 | * - Filter bogus Ctrl+ combinations. 51 | * 52 | * List of escape sequences used by this program, we do everything just 53 | * with three sequences. In order to be so cheap we may have some 54 | * flickering effect with some slow terminal, but the lesser sequences 55 | * the more compatible. 56 | * 57 | * EL (Erase Line) 58 | * Sequence: ESC [ n K 59 | * Effect: if n is 0 or missing, clear from cursor to end of line 60 | * Effect: if n is 1, clear from beginning of line to cursor 61 | * Effect: if n is 2, clear entire line 62 | * 63 | * CUF (CUrsor Forward) 64 | * Sequence: ESC [ n C 65 | * Effect: moves cursor forward n chars 66 | * 67 | * CUB (CUrsor Backward) 68 | * Sequence: ESC [ n D 69 | * Effect: moves cursor backward n chars 70 | * 71 | * The following is used to get the terminal width if getting 72 | * the width with the TIOCGWINSZ ioctl fails 73 | * 74 | * DSR (Device Status Report) 75 | * Sequence: ESC [ 6 n 76 | * Effect: reports the current cusor position as ESC [ n ; m R 77 | * where n is the row and m is the column 78 | * 79 | * When multi line mode is enabled, we also use an additional escape 80 | * sequence. However multi line editing is disabled by default. 81 | * 82 | * CUU (Cursor Up) 83 | * Sequence: ESC [ n A 84 | * Effect: moves cursor up of n chars. 85 | * 86 | * CUD (Cursor Down) 87 | * Sequence: ESC [ n B 88 | * Effect: moves cursor down of n chars. 89 | * 90 | * When linenoiseClearScreen() is called, two additional escape sequences 91 | * are used in order to clear the screen and position the cursor at home 92 | * position. 93 | * 94 | * CUP (Cursor position) 95 | * Sequence: ESC [ H 96 | * Effect: moves the cursor to upper left corner 97 | * 98 | * ED (Erase display) 99 | * Sequence: ESC [ 2 J 100 | * Effect: clear the whole screen 101 | * 102 | */ 103 | 104 | #ifndef _WIN32 105 | #include 106 | #include 107 | #include 108 | #include 109 | #endif 110 | 111 | #include 112 | #include 113 | #include 114 | #include 115 | #include 116 | #include 117 | #include "linenoise.h" 118 | 119 | #ifdef _WIN32 120 | #include 121 | #include 122 | #define isatty _isatty 123 | #define snprintf _snprintf 124 | #define __NOTUSED(V) ((void) V) 125 | #endif 126 | 127 | #define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100 128 | #define LINENOISE_MAX_LINE 4096 129 | static const char *unsupported_term[] = {"dumb","cons25","emacs",NULL}; 130 | static linenoiseCompletionCallback *completionCallback = NULL; 131 | 132 | #ifndef _WIN32 133 | static struct termios orig_termios; /* In order to restore at exit.*/ 134 | static int orig_file_flags; /* In order to restore at exit.*/ 135 | #endif 136 | 137 | static int rawmode = 0; /* For atexit() function to check if restore is needed*/ 138 | static int mlmode = 0; /* Multi line mode. Default is single line. */ 139 | static int atexit_registered = 0; /* Register atexit just 1 time. */ 140 | static int history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN; 141 | static int history_len = 0; 142 | static char **history = NULL; 143 | 144 | /* for ctrl-r */ 145 | static char *search_buf = NULL; 146 | static int search_pos = -1; 147 | 148 | /* The linenoiseState structure represents the state during line editing. 149 | * We pass this state to functions implementing specific editing 150 | * functionalities. */ 151 | struct linenoiseState { 152 | int ifd; /* Terminal stdin file descriptor. */ 153 | int ofd; /* Terminal stdout file descriptor. */ 154 | char *buf; /* Edited line buffer. */ 155 | size_t buflen; /* Edited line buffer size. */ 156 | const char *prompt; /* Prompt to display. */ 157 | size_t plen; /* Prompt length. */ 158 | size_t pos; /* Current cursor position. */ 159 | size_t oldpos; /* Previous refresh cursor position. */ 160 | size_t len; /* Current edited line length. */ 161 | size_t cols; /* Number of columns in terminal. */ 162 | size_t maxrows; /* Maximum num of rows used so far (multiline mode) */ 163 | int history_index; /* The history index we are currently editing. */ 164 | }; 165 | 166 | enum KEY_ACTION{ 167 | KEY_NULL = 0, /* NULL */ 168 | CTRL_A = 1, /* Ctrl+a */ 169 | CTRL_B = 2, /* Ctrl-b */ 170 | CTRL_C = 3, /* Ctrl-c */ 171 | CTRL_D = 4, /* Ctrl-d */ 172 | CTRL_E = 5, /* Ctrl-e */ 173 | CTRL_F = 6, /* Ctrl-f */ 174 | CTRL_H = 8, /* Ctrl-h */ 175 | TAB = 9, /* Tab */ 176 | CTRL_K = 11, /* Ctrl+k */ 177 | CTRL_L = 12, /* Ctrl+l */ 178 | ENTER = 13, /* Enter */ 179 | CTRL_N = 14, /* Ctrl-n */ 180 | CTRL_P = 16, /* Ctrl-p */ 181 | CTRL_R = 18, /* Ctrl-p */ 182 | CTRL_T = 20, /* Ctrl-t */ 183 | CTRL_U = 21, /* Ctrl+u */ 184 | CTRL_W = 23, /* Ctrl+w */ 185 | ESC = 27, /* Escape */ 186 | BACKSPACE = 127 /* Backspace */ 187 | }; 188 | 189 | static void linenoiseAtExit(void); 190 | int linenoiseHistoryAdd(const char *line); 191 | static void linenoiseHistoryCacheSearchBuf(char **cache, char *buf_data); 192 | static int linenoiseHistorySearch(char *str); 193 | 194 | static void refreshLine(struct linenoiseState *l); 195 | 196 | #ifdef _WIN32 197 | #ifndef STDIN_FILENO 198 | #define STDIN_FILENO (_fileno(stdin)) 199 | #endif 200 | #ifndef STDOUT_FILENO 201 | #define STDOUT_FILENO (_fileno(stdout)) 202 | #endif 203 | 204 | static HANDLE hOut; 205 | static HANDLE hIn; 206 | static DWORD consolemode; 207 | 208 | static int win32read(char *c, int length) { 209 | 210 | DWORD num_read; 211 | INPUT_RECORD b; 212 | KEY_EVENT_RECORD e; 213 | 214 | if (length <= 0) return 0; 215 | /* TODO: The rest of this function currently ignores 'length'. */ 216 | 217 | while (1) { 218 | if (!ReadConsoleInput(hIn, &b, 1, &num_read)) return 0; 219 | if (!num_read) return 0; 220 | 221 | if (b.EventType == KEY_EVENT && b.Event.KeyEvent.bKeyDown) { 222 | 223 | e = b.Event.KeyEvent; 224 | *c = e.wVirtualKeyCode; 225 | 226 | //if (e.dwControlKeyState & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED)) { 227 | /* Alt+key ignored */ 228 | //} else 229 | /* If the ASCII code looks control-ish and a CTRL modified was used, 230 | * handle selected CTRL-* combinations. 231 | * Note: We must not do this based on the CTRL modifiers alone, 232 | * because depending on the keyboard layout, certain characters 233 | * can only be typed using CTRL modifiers (e.g. '{' which is 234 | * AltGr+7 on a German keyboard, AltGr being flagged as ALT+CTRL.) 235 | */ 236 | if (((unsigned char)b.Event.KeyEvent.uChar.AsciiChar < 32) 237 | && 238 | (e.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED))) { 239 | /* Ctrl+Key */ 240 | switch (*c) { 241 | case 'D': 242 | case 'C': 243 | case 'H': 244 | case 'R': /* ctrl-r, history search */ 245 | case 'T': 246 | case 'B': /* ctrl-b, left_arrow */ 247 | case 'F': /* ctrl-f right_arrow*/ 248 | case 'P': /* ctrl-p up_arrow*/ 249 | case 'N': /* ctrl-n down_arrow*/ 250 | case 'U': /* Ctrl+u, delete the whole line. */ 251 | case 'K': /* Ctrl+k, delete from current to end of line. */ 252 | case 'A': /* Ctrl+a, go to the start of the line */ 253 | case 'E': /* ctrl+e, go to the end of the line */ 254 | case 'L': /* ctrl+l, clear screen */ 255 | case 'W': /* ctrl+w, delete previous word */ 256 | *c = *c - 'A' + 1; 257 | return (int)num_read; 258 | } 259 | 260 | /* Other Ctrl+KEYs ignored */ 261 | } else { 262 | switch (*c) { 263 | 264 | case VK_ESCAPE: /* ignore - send ctrl-c, will return -1 */ 265 | *c = 3; 266 | return 1; 267 | case VK_RETURN: /* enter */ 268 | *c = 13; 269 | return 1; 270 | case VK_LEFT: /* left */ 271 | *c = 2; 272 | return 1; 273 | case VK_RIGHT: /* right */ 274 | *c = 6; 275 | return 1; 276 | case VK_UP: /* up */ 277 | *c = 16; 278 | return 1; 279 | case VK_DOWN: /* down */ 280 | *c = 14; 281 | return 1; 282 | case VK_HOME: 283 | *c = 1; 284 | return 1; 285 | case VK_END: 286 | *c = 5; 287 | return 1; 288 | case VK_BACK: 289 | *c = 8; 290 | return 1; 291 | case VK_DELETE: 292 | *c = 4; 293 | return 1; 294 | default: 295 | if (*c = e.uChar.AsciiChar) return (int)num_read; 296 | } 297 | } 298 | } 299 | } 300 | 301 | return -1; /* Makes compiler happy */ 302 | } 303 | 304 | /* ======================= Clear Screen API for Windows ====================== */ 305 | 306 | /* see http://support.microsoft.com/kb/99261 */ 307 | 308 | /* Standard error macro for reporting API errors */ 309 | #define PERR(bSuccess, api) if(!(bSuccess)) \ 310 | printf("%s:Error %d from %s on line %d\n", __FILE__, GetLastError(), api, __LINE__); 311 | 312 | static void cls( HANDLE hConsole ) 313 | { 314 | COORD coordScreen = { 0, 0 }; /* here's where we'll home the 315 | cursor */ 316 | BOOL bSuccess; 317 | DWORD cCharsWritten; 318 | CONSOLE_SCREEN_BUFFER_INFO csbi; /* to get buffer info */ 319 | DWORD dwConSize; /* number of character cells in 320 | the current buffer */ 321 | 322 | /* get the number of character cells in the current buffer */ 323 | bSuccess = GetConsoleScreenBufferInfo( hConsole, &csbi ); 324 | #ifdef LN_DEBUG 325 | PERR( bSuccess, "GetConsoleScreenBufferInfo" ); 326 | #endif 327 | dwConSize = csbi.dwSize.X * csbi.dwSize.Y; 328 | 329 | /* fill the entire screen with blanks */ 330 | bSuccess = FillConsoleOutputCharacter( hConsole, (TCHAR) ' ', 331 | dwConSize, coordScreen, &cCharsWritten ); 332 | #ifdef LN_DEBUG 333 | PERR( bSuccess, "FillConsoleOutputCharacter" ); 334 | #endif 335 | 336 | /* get the current text attribute */ 337 | bSuccess = GetConsoleScreenBufferInfo( hConsole, &csbi ); 338 | #ifdef LN_DEBUG 339 | PERR( bSuccess, "ConsoleScreenBufferInfo" ); 340 | #endif 341 | 342 | /* now set the buffer's attributes accordingly */ 343 | bSuccess = FillConsoleOutputAttribute( hConsole, csbi.wAttributes, 344 | dwConSize, coordScreen, &cCharsWritten ); 345 | #ifdef LN_DEBUG 346 | PERR( bSuccess, "FillConsoleOutputAttribute" ); 347 | #endif 348 | 349 | /* put the cursor at (0, 0) */ 350 | bSuccess = SetConsoleCursorPosition( hConsole, coordScreen ); 351 | #ifdef LN_DEBUG 352 | PERR( bSuccess, "SetConsoleCursorPosition" ); 353 | #endif 354 | return; 355 | } 356 | 357 | #ifdef __STRICT_ANSI__ 358 | static char *strdup(const char *s) { 359 | size_t l = strlen(s)+1; 360 | char *p = malloc(l); 361 | 362 | memcpy(p,s,l); 363 | return p; 364 | } 365 | #endif /* __STRICT_ANSI__ */ 366 | 367 | #endif /* _WIN32 */ 368 | 369 | /* Debugging macro. */ 370 | #if 0 371 | FILE *lndebug_fp = NULL; 372 | #define lndebug(...) \ 373 | do { \ 374 | if (lndebug_fp == NULL) { \ 375 | lndebug_fp = fopen("/tmp/lndebug.txt","a"); \ 376 | fprintf(lndebug_fp, \ 377 | "[%d %d %d] p: %d, rows: %d, rpos: %d, max: %d, oldmax: %d\n", \ 378 | (int)l->len,(int)l->pos,(int)l->oldpos,plen,rows,rpos, \ 379 | (int)l->maxrows,old_rows); \ 380 | } \ 381 | fprintf(lndebug_fp, ", " __VA_ARGS__); \ 382 | fflush(lndebug_fp); \ 383 | } while (0) 384 | #else 385 | #define lndebug(fmt, ...) 386 | #endif 387 | 388 | /* ======================= Low level terminal handling ====================== */ 389 | 390 | /* Set if to use or not the multi line mode. */ 391 | void linenoiseSetMultiLine(int ml) { 392 | mlmode = ml; 393 | } 394 | 395 | /* Return true if the terminal name is in the list of terminals we know are 396 | * not able to understand basic escape sequences. */ 397 | static int isUnsupportedTerm(void) { 398 | #ifndef _WIN32 399 | char *term = getenv("TERM"); 400 | int j; 401 | 402 | if (term == NULL) return 0; 403 | for (j = 0; unsupported_term[j]; j++) 404 | if (!strcasecmp(term,unsupported_term[j])) return 1; 405 | #endif 406 | return 0; 407 | } 408 | 409 | /* Raw mode: 1960 magic shit. */ 410 | static int enableRawMode(int fd) { 411 | #ifndef _WIN32 412 | struct termios raw; 413 | 414 | if (!isatty(fd)) goto fatal; 415 | if (!atexit_registered) { 416 | atexit(linenoiseAtExit); 417 | atexit_registered = 1; 418 | } 419 | 420 | orig_file_flags = fcntl(fd, F_GETFL, 0); 421 | 422 | if (tcgetattr(fd,&orig_termios) == -1) goto fatal; 423 | 424 | raw = orig_termios; /* modify the original mode */ 425 | /* input modes: no break, no CR to NL, no parity check, no strip char, 426 | * no start/stop output control. */ 427 | raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); 428 | /* output modes - disable post processing */ 429 | raw.c_oflag &= ~(OPOST); 430 | /* control modes - set 8 bit chars */ 431 | raw.c_cflag |= (CS8); 432 | /* local modes - echoing off, canonical off, no extended functions, 433 | * no signal chars (^Z,^C) */ 434 | raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); 435 | /* control chars - set return condition: min number of bytes and timer. 436 | * We want read to return every single byte, without timeout. */ 437 | raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */ 438 | 439 | #ifndef _WIN32 440 | /* IEEE Std 1003.1-2001 does not specify whether the setting of O_NONBLOCK takes precedence 441 | * over MIN or TIME settings. Therefore, if O_NONBLOCK is set, read() may return immediately, 442 | * regardless of the setting of MIN or TIME. Also, if no data is available, read() may either 443 | * return 0, or return -1 with errno set to [EAGAIN]. 444 | * See http://pubs.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap11.html#tag_11_01_07 */ 445 | fcntl(fd, F_SETFL, orig_file_flags & ~O_NONBLOCK); 446 | #endif 447 | 448 | /* put terminal in raw mode after flushing */ 449 | if (tcsetattr(fd,TCSAFLUSH,&raw) < 0) goto fatal; 450 | rawmode = 1; 451 | #else 452 | __NOTUSED(fd); 453 | 454 | if (!atexit_registered) { 455 | /* Init windows console handles only once */ 456 | hOut = GetStdHandle(STD_OUTPUT_HANDLE); 457 | if (hOut==INVALID_HANDLE_VALUE) goto fatal; 458 | 459 | if (!GetConsoleMode(hOut, &consolemode)) { 460 | CloseHandle(hOut); 461 | errno = ENOTTY; 462 | return -1; 463 | }; 464 | 465 | hIn = GetStdHandle(STD_INPUT_HANDLE); 466 | if (hIn == INVALID_HANDLE_VALUE) { 467 | CloseHandle(hOut); 468 | errno = ENOTTY; 469 | return -1; 470 | } 471 | 472 | GetConsoleMode(hIn, &consolemode); 473 | SetConsoleMode(hIn, ENABLE_LINE_INPUT); 474 | 475 | /* Cleanup them at exit */ 476 | atexit(linenoiseAtExit); 477 | atexit_registered = 1; 478 | } 479 | 480 | rawmode = 1; 481 | #endif 482 | return 0; 483 | 484 | fatal: 485 | errno = ENOTTY; 486 | return -1; 487 | } 488 | 489 | static void disableRawMode(int fd) { 490 | #ifdef _WIN32 491 | __NOTUSED(fd); 492 | rawmode = 0; 493 | #else 494 | /* Don't even check the return value as it's too late. */ 495 | if (rawmode && tcsetattr(fd,TCSAFLUSH,&orig_termios) != -1) 496 | rawmode = 0; 497 | 498 | if (orig_file_flags != -1) 499 | fcntl(fd, F_SETFL, orig_file_flags); 500 | #endif 501 | } 502 | 503 | /* Use the ESC [6n escape sequence to query the horizontal cursor position 504 | * and return it. On error -1 is returned, on success the position of the 505 | * cursor. */ 506 | static int getCursorPosition(int ifd, int ofd) { 507 | char buf[32]; 508 | int cols, rows; 509 | unsigned int i = 0; 510 | 511 | /* Report cursor location */ 512 | if (write(ofd, "\x1b[6n", 4) != 4) return -1; 513 | 514 | /* Read the response: ESC [ rows ; cols R */ 515 | while (i < sizeof(buf)-1) { 516 | if (read(ifd,buf+i,1) != 1) break; 517 | if (buf[i] == 'R') break; 518 | i++; 519 | } 520 | buf[i] = '\0'; 521 | 522 | /* Parse it. */ 523 | if (buf[0] != ESC || buf[1] != '[') return -1; 524 | if (sscanf(buf+2,"%d;%d",&rows,&cols) != 2) return -1; 525 | return cols; 526 | } 527 | 528 | /* Try to get the number of columns in the current terminal, or assume 80 529 | * if it fails. */ 530 | static int getColumns(int ifd, int ofd) { 531 | #ifdef _WIN32 532 | CONSOLE_SCREEN_BUFFER_INFO b; 533 | 534 | if (!GetConsoleScreenBufferInfo(hOut, &b)) return 80; 535 | return b.srWindow.Right - b.srWindow.Left; 536 | #else 537 | struct winsize ws; 538 | 539 | if (ioctl(1, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) { 540 | /* ioctl() failed. Try to query the terminal itself. */ 541 | int start, cols; 542 | 543 | /* Get the initial position so we can restore it later. */ 544 | start = getCursorPosition(ifd,ofd); 545 | if (start == -1) goto failed; 546 | 547 | /* Go to right margin and get position. */ 548 | if (write(ofd,"\x1b[999C",6) != 6) goto failed; 549 | cols = getCursorPosition(ifd,ofd); 550 | if (cols == -1) goto failed; 551 | 552 | /* Restore position. */ 553 | if (cols > start) { 554 | char seq[32]; 555 | snprintf(seq,32,"\x1b[%dD",cols-start); 556 | if (write(ofd,seq,strlen(seq)) == -1) { 557 | /* Can't recover... */ 558 | } 559 | } 560 | return cols; 561 | } else { 562 | return ws.ws_col; 563 | } 564 | 565 | failed: 566 | return 80; 567 | #endif 568 | } 569 | 570 | /* Clear the screen. Used to handle ctrl+l */ 571 | void linenoiseClearScreen(void) { 572 | #ifdef _WIN32 573 | cls(hOut); 574 | #else 575 | if (write(STDOUT_FILENO,"\x1b[H\x1b[2J",7) <= 0) { 576 | /* nothing to do, just to avoid warning. */ 577 | } 578 | #endif 579 | } 580 | 581 | /* Beep, used for completion when there is nothing to complete or when all 582 | * the choices were already shown. */ 583 | static void linenoiseBeep(void) { 584 | fprintf(stderr, "\x7"); 585 | fflush(stderr); 586 | } 587 | 588 | /* ============================== Completion ================================ */ 589 | 590 | /* Free a list of completion option populated by linenoiseAddCompletion(). */ 591 | static void freeCompletions(linenoiseCompletions *lc) { 592 | size_t i; 593 | for (i = 0; i < lc->len; i++) 594 | free(lc->cvec[i]); 595 | if (lc->cvec != NULL) 596 | free(lc->cvec); 597 | } 598 | 599 | /* This is an helper function for linenoiseEdit() and is called when the 600 | * user types the key in order to complete the string currently in the 601 | * input. 602 | * 603 | * The state of the editing is encapsulated into the pointed linenoiseState 604 | * structure as described in the structure definition. */ 605 | static int completeLine(struct linenoiseState *ls) { 606 | linenoiseCompletions lc = { 0, NULL }; 607 | int nread, nwritten; 608 | char c = 0; 609 | 610 | completionCallback(ls->buf,&lc); 611 | if (lc.len == 0) { 612 | linenoiseBeep(); 613 | } else { 614 | size_t stop = 0, i = 0; 615 | 616 | while(!stop) { 617 | /* Show completion or original buffer */ 618 | if (i < lc.len) { 619 | struct linenoiseState saved = *ls; 620 | 621 | ls->len = ls->pos = strlen(lc.cvec[i]); 622 | ls->buf = lc.cvec[i]; 623 | refreshLine(ls); 624 | ls->len = saved.len; 625 | ls->pos = saved.pos; 626 | ls->buf = saved.buf; 627 | } else { 628 | refreshLine(ls); 629 | } 630 | 631 | #ifdef _WIN32 632 | nread = win32read(&c, 1); 633 | #else 634 | nread = read(ls->ifd,&c,1); 635 | #endif 636 | if (nread <= 0) { 637 | freeCompletions(&lc); 638 | return -1; 639 | } 640 | 641 | switch(c) { 642 | case 9: /* tab */ 643 | i = (i+1) % (lc.len+1); 644 | if (i == lc.len) linenoiseBeep(); 645 | break; 646 | case 27: /* escape */ 647 | /* Re-show original buffer */ 648 | if (i < lc.len) refreshLine(ls); 649 | stop = 1; 650 | break; 651 | default: 652 | /* Update buffer and return */ 653 | if (i < lc.len) { 654 | nwritten = snprintf(ls->buf,ls->buflen,"%s",lc.cvec[i]); 655 | ls->len = ls->pos = nwritten; 656 | } 657 | stop = 1; 658 | break; 659 | } 660 | } 661 | } 662 | 663 | freeCompletions(&lc); 664 | return c; /* Return last read character */ 665 | } 666 | 667 | /* Register a callback function to be called for tab-completion. */ 668 | void linenoiseSetCompletionCallback(linenoiseCompletionCallback *fn) { 669 | completionCallback = fn; 670 | } 671 | 672 | /* This function is used by the callback function registered by the user 673 | * in order to add completion options given the input string when the 674 | * user typed . See the example.c source code for a very easy to 675 | * understand example. */ 676 | void linenoiseAddCompletion(linenoiseCompletions *lc, const char *str) { 677 | size_t len = strlen(str); 678 | char *copy, **cvec; 679 | 680 | copy =(char*) malloc(len+1); 681 | if (copy == NULL) return; 682 | memcpy(copy,str,len+1); 683 | cvec = (char**)realloc(lc->cvec,sizeof(char*)*(lc->len+1)); 684 | if (cvec == NULL) { 685 | free(copy); 686 | return; 687 | } 688 | lc->cvec = cvec; 689 | lc->cvec[lc->len++] = copy; 690 | } 691 | 692 | /* =========================== Line editing ================================= */ 693 | 694 | /* We define a very simple "append buffer" structure, that is an heap 695 | * allocated string where we can append to. This is useful in order to 696 | * write all the escape sequences in a buffer and flush them to the standard 697 | * output in a single call, to avoid flickering effects. */ 698 | struct abuf { 699 | char *b; 700 | int len; 701 | }; 702 | 703 | static void abInit(struct abuf *ab) { 704 | ab->b = NULL; 705 | ab->len = 0; 706 | } 707 | 708 | static void abAppend(struct abuf *ab, const char *s, int len) { 709 | char *new = realloc(ab->b,ab->len+len); 710 | 711 | if (new == NULL) return; 712 | memcpy(new+ab->len,s,len); 713 | ab->b = new; 714 | ab->len += len; 715 | } 716 | 717 | static void abFree(struct abuf *ab) { 718 | free(ab->b); 719 | } 720 | 721 | /* Single line low level line refresh. 722 | * 723 | * Rewrite the currently edited line accordingly to the buffer content, 724 | * cursor position, and number of columns of the terminal. */ 725 | static void refreshSingleLine(struct linenoiseState *l) { 726 | char seq[64]; 727 | 728 | #ifdef _WIN32 729 | DWORD pl, bl, w; 730 | CONSOLE_SCREEN_BUFFER_INFO b; 731 | COORD coord; 732 | #endif 733 | 734 | const size_t plen = strlen(l->prompt); 735 | int fd = l->ofd; 736 | char *buf = l->buf; 737 | size_t len = l->len; 738 | size_t pos = l->pos; 739 | struct abuf ab; 740 | 741 | while((plen+pos) >= l->cols) { 742 | buf++; 743 | len--; 744 | pos--; 745 | } 746 | while (plen+len > l->cols) { 747 | len--; 748 | } 749 | 750 | #ifdef _WIN32 751 | __NOTUSED(seq); 752 | __NOTUSED(fd); 753 | 754 | /* Get buffer console info */ 755 | if (!GetConsoleScreenBufferInfo(hOut, &b)) return; 756 | /* Erase Line */ 757 | coord.X = 0; 758 | coord.Y = b.dwCursorPosition.Y; 759 | FillConsoleOutputCharacterA(hOut, ' ', b.dwSize.X, coord, &w); 760 | /* Cursor to the left edge */ 761 | SetConsoleCursorPosition(hOut, coord); 762 | /* Write the prompt and the current buffer content */ 763 | WriteConsole(hOut, l->prompt, (DWORD)plen, &pl, NULL); 764 | WriteConsole(hOut, buf, (DWORD)len, &bl, NULL); 765 | /* Move cursor to original position. */ 766 | coord.X = (int)(pos+plen); 767 | coord.Y = b.dwCursorPosition.Y; 768 | SetConsoleCursorPosition(hOut, coord); 769 | #else 770 | 771 | abInit(&ab); 772 | /* Cursor to left edge */ 773 | snprintf(seq,64,"\r"); 774 | abAppend(&ab,seq,strlen(seq)); 775 | /* Write the prompt and the current buffer content */ 776 | abAppend(&ab,l->prompt,strlen(l->prompt)); 777 | abAppend(&ab,buf,len); 778 | /* Erase to right */ 779 | snprintf(seq,64,"\x1b[0K"); 780 | abAppend(&ab,seq,strlen(seq)); 781 | /* Move cursor to original position. */ 782 | snprintf(seq,64,"\r\x1b[%dC", (int)(pos+plen)); 783 | abAppend(&ab,seq,strlen(seq)); 784 | if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */ 785 | abFree(&ab); 786 | #endif 787 | } 788 | 789 | /* Multi line low level line refresh. 790 | * 791 | * Rewrite the currently edited line accordingly to the buffer content, 792 | * cursor position, and number of columns of the terminal. 793 | * TODO: refreshMultiLine didn't work on Windows yet. 794 | * Any one who wants to port it to windows could take some ideas from 795 | * libuv, see uv_tty_write_bufs() function in src/win/tty.c */ 796 | static void refreshMultiLine(struct linenoiseState *l) { 797 | char seq[64]; 798 | int plen = strlen(l->prompt); 799 | int rows = (plen+l->len+l->cols-1)/l->cols; /* rows used by current buf. */ 800 | int rpos = (plen+l->oldpos+l->cols)/l->cols; /* cursor relative row. */ 801 | int rpos2; /* rpos after refresh. */ 802 | int col; /* colum position, zero-based. */ 803 | int old_rows = l->maxrows; 804 | int fd = l->ofd, j; 805 | struct abuf ab; 806 | 807 | /* Update maxrows if needed. */ 808 | if (rows > (int)l->maxrows) l->maxrows = rows; 809 | 810 | /* First step: clear all the lines used before. To do so start by 811 | * going to the last row. */ 812 | abInit(&ab); 813 | if (old_rows-rpos > 0) { 814 | lndebug("go down %d", old_rows-rpos); 815 | snprintf(seq,64,"\x1b[%dB", old_rows-rpos); 816 | abAppend(&ab,seq,strlen(seq)); 817 | } 818 | 819 | /* Now for every row clear it, go up. */ 820 | for (j = 0; j < old_rows-1; j++) { 821 | lndebug("clear+up"); 822 | snprintf(seq,64,"\r\x1b[0K\x1b[1A"); 823 | abAppend(&ab,seq,strlen(seq)); 824 | } 825 | 826 | /* Clean the top line. */ 827 | lndebug("clear"); 828 | snprintf(seq,64,"\r\x1b[0G\x1b[0K"); 829 | 830 | abAppend(&ab,seq,strlen(seq)); 831 | 832 | /* Write the prompt and the current buffer content */ 833 | abAppend(&ab,l->prompt,strlen(l->prompt)); 834 | abAppend(&ab,l->buf,l->len); 835 | 836 | /* If we are at the very end of the screen with our prompt, we need to 837 | * emit a newline and move the prompt to the first column. */ 838 | if (l->pos && 839 | l->pos == l->len && 840 | (l->pos+plen) % l->cols == 0) 841 | { 842 | lndebug(""); 843 | abAppend(&ab,"\n",1); 844 | snprintf(seq,64,"\r"); 845 | abAppend(&ab,seq,strlen(seq)); 846 | rows++; 847 | if (rows > (int)l->maxrows) l->maxrows = rows; 848 | } 849 | 850 | /* Move cursor to right position. */ 851 | rpos2 = (plen+l->pos+l->cols)/l->cols; /* current cursor relative row. */ 852 | lndebug("rpos2 %d", rpos2); 853 | 854 | /* Go up till we reach the expected positon. */ 855 | if (rows-rpos2 > 0) { 856 | lndebug("go-up %d", rows-rpos2); 857 | snprintf(seq,64,"\x1b[%dA", rows-rpos2); 858 | abAppend(&ab,seq,strlen(seq)); 859 | } 860 | 861 | /* Set column. */ 862 | col = (plen+(int)l->pos) % (int)l->cols; 863 | lndebug("set col %d", 1+col); 864 | if (col) 865 | snprintf(seq,64,"\r\x1b[%dC", col); 866 | else 867 | snprintf(seq,64,"\r"); 868 | abAppend(&ab,seq,strlen(seq)); 869 | 870 | lndebug("\n"); 871 | l->oldpos = l->pos; 872 | 873 | if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */ 874 | abFree(&ab); 875 | } 876 | 877 | /* Calls the two low level functions refreshSingleLine() or 878 | * refreshMultiLine() according to the selected mode. */ 879 | static void refreshLine(struct linenoiseState *l) { 880 | if (mlmode) 881 | refreshMultiLine(l); 882 | else 883 | refreshSingleLine(l); 884 | } 885 | 886 | /* Insert the character 'c' at cursor current position. 887 | * 888 | * On error writing to the terminal -1 is returned, otherwise 0. */ 889 | static int linenoiseEditInsert(struct linenoiseState *l, int c) { 890 | 891 | #ifdef _WIN32 892 | DWORD foo; 893 | #endif 894 | 895 | if (l->len < l->buflen) { 896 | if (l->len == l->pos) { 897 | l->buf[l->pos] = c; 898 | l->pos++; 899 | l->len++; 900 | l->buf[l->len] = '\0'; 901 | if ((!mlmode && l->plen+l->len < l->cols) /* || mlmode */) { 902 | /* Avoid a full update of the line in the 903 | * trivial case. */ 904 | #ifdef _WIN32 905 | if (!WriteConsole(hOut, &c, 1, &foo, NULL)) return -1; 906 | #else 907 | if (write(l->ofd,&c,1) == -1) return -1; 908 | #endif 909 | } else { 910 | refreshLine(l); 911 | } 912 | } else { 913 | memmove(l->buf+l->pos+1,l->buf+l->pos,l->len-l->pos); 914 | l->buf[l->pos] = c; 915 | l->len++; 916 | l->pos++; 917 | l->buf[l->len] = '\0'; 918 | refreshLine(l); 919 | } 920 | } 921 | return 0; 922 | } 923 | 924 | /* Move cursor on the left. */ 925 | static void linenoiseEditMoveLeft(struct linenoiseState *l) { 926 | if (l->pos > 0) { 927 | l->pos--; 928 | refreshLine(l); 929 | } 930 | } 931 | 932 | /* Move cursor on the right. */ 933 | static void linenoiseEditMoveRight(struct linenoiseState *l) { 934 | if (l->pos != l->len) { 935 | l->pos++; 936 | refreshLine(l); 937 | } 938 | } 939 | 940 | /* Move cursor to the start of the line. */ 941 | void linenoiseEditMoveHome(struct linenoiseState *l) { 942 | if (l->pos != 0) { 943 | l->pos = 0; 944 | refreshLine(l); 945 | } 946 | } 947 | 948 | /* Move cursor to the end of the line. */ 949 | void linenoiseEditMoveEnd(struct linenoiseState *l) { 950 | if (l->pos != l->len) { 951 | l->pos = l->len; 952 | refreshLine(l); 953 | } 954 | } 955 | 956 | /* Substitute the currently edited line with the next or previous history 957 | * entry as specified by 'dir'. */ 958 | #define LINENOISE_HISTORY_NEXT 0 959 | #define LINENOISE_HISTORY_PREV 1 960 | static void linenoiseEditHistoryNext(struct linenoiseState *l, int dir) { 961 | if (history_len > 1) { 962 | /* Update the current history entry before to 963 | * overwrite it with the next one. */ 964 | free(history[history_len - 1 - l->history_index]); 965 | history[history_len - 1 - l->history_index] = strdup(l->buf); 966 | /* Show the new entry */ 967 | l->history_index += (dir == LINENOISE_HISTORY_PREV) ? 1 : -1; 968 | if (l->history_index < 0) { 969 | l->history_index = 0; 970 | return; 971 | } else if (l->history_index >= history_len) { 972 | l->history_index = history_len-1; 973 | return; 974 | } 975 | strncpy(l->buf,history[history_len - 1 - l->history_index],l->buflen); 976 | l->buf[l->buflen-1] = '\0'; 977 | l->len = l->pos = strlen(l->buf); 978 | refreshLine(l); 979 | } 980 | } 981 | 982 | /* Delete the character at the right of the cursor without altering the cursor 983 | * position. Basically this is what happens with the "Delete" keyboard key. */ 984 | static void linenoiseEditDelete(struct linenoiseState *l) { 985 | if (l->len > 0 && l->pos < l->len) { 986 | memmove(l->buf+l->pos,l->buf+l->pos+1,l->len-l->pos-1); 987 | l->len--; 988 | l->buf[l->len] = '\0'; 989 | refreshLine(l); 990 | } 991 | } 992 | 993 | /* Backspace implementation. */ 994 | static void linenoiseEditBackspace(struct linenoiseState *l) { 995 | if (l->pos > 0 && l->len > 0) { 996 | memmove(l->buf+l->pos-1,l->buf+l->pos,l->len-l->pos); 997 | l->pos--; 998 | l->len--; 999 | l->buf[l->len] = '\0'; 1000 | refreshLine(l); 1001 | } 1002 | } 1003 | 1004 | /* Delete the previous word, maintaining the cursor at the start of the 1005 | * current word. */ 1006 | static void linenoiseEditDeletePrevWord(struct linenoiseState *l) { 1007 | size_t old_pos = l->pos; 1008 | size_t diff; 1009 | 1010 | while (l->pos > 0 && l->buf[l->pos-1] == ' ') 1011 | l->pos--; 1012 | while (l->pos > 0 && l->buf[l->pos-1] != ' ') 1013 | l->pos--; 1014 | diff = old_pos - l->pos; 1015 | memmove(l->buf+l->pos,l->buf+old_pos,l->len-old_pos+1); 1016 | l->len -= diff; 1017 | refreshLine(l); 1018 | } 1019 | 1020 | static void linenoiseEditDeleteNextWord(struct linenoiseState *l) { 1021 | size_t old_pos = l->pos; 1022 | 1023 | while (l->pos < l->len && l->buf[l->pos] == ' ') 1024 | l->pos++; 1025 | while (l->pos < l->len && l->buf[l->pos] != ' ') 1026 | l->pos++; 1027 | memmove(l->buf + old_pos, l->buf + l->pos, l->len - l->pos); 1028 | l->len -= l->pos - old_pos; 1029 | l->pos = old_pos; 1030 | refreshLine(l); 1031 | } 1032 | 1033 | static void linenoiseEditMovePrevWord(struct linenoiseState *l) { 1034 | while (l->pos > 0) { 1035 | l->pos--; 1036 | if (l->buf[l->pos] != ' ' && l->buf[l->pos-1] == ' ') break; 1037 | } 1038 | refreshLine(l); 1039 | } 1040 | 1041 | static void linenoiseEditMoveNextWord(struct linenoiseState *l) { 1042 | while (l->pos < l->len) { 1043 | l->pos++; 1044 | if (l->buf[l->pos] == ' ' && l->buf[l->pos-1] != ' ') break; 1045 | } 1046 | 1047 | refreshLine(l); 1048 | } 1049 | 1050 | /* This function is the core of the line editing capability of linenoise. 1051 | * It expects 'fd' to be already in "raw mode" so that every key pressed 1052 | * will be returned ASAP to read(). 1053 | * 1054 | * The resulting string is put into 'buf' when the user type enter, or 1055 | * when ctrl+d is typed. 1056 | * 1057 | * The function returns the length of the current buffer. */ 1058 | static int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen, const char *prompt) 1059 | { 1060 | struct linenoiseState l; 1061 | #ifdef _WIN32 1062 | DWORD foo; 1063 | #endif 1064 | 1065 | /* Populate the linenoise state that we pass to functions implementing 1066 | * specific editing functionalities. */ 1067 | l.ifd = stdin_fd; 1068 | l.ofd = stdout_fd; 1069 | l.buf = buf; 1070 | l.buflen = buflen; 1071 | l.prompt = prompt; 1072 | l.plen = strlen(prompt); 1073 | l.oldpos = l.pos = 0; 1074 | l.len = 0; 1075 | l.cols = getColumns(stdin_fd, stdout_fd); 1076 | l.maxrows = 0; 1077 | l.history_index = 0; 1078 | 1079 | /* Buffer starts empty. */ 1080 | l.buf[0] = '\0'; 1081 | l.buflen--; /* Make sure there is always space for the nulterm */ 1082 | 1083 | /* The latest history entry is always our current buffer, that 1084 | * initially is just an empty string. */ 1085 | linenoiseHistoryAdd(""); 1086 | 1087 | #ifdef _WIN32 1088 | if (!WriteConsole(hOut, prompt, (DWORD)l.plen, &foo, NULL)) return -1; 1089 | #else 1090 | if (write(l.ofd,prompt,l.plen) == -1) return -1; 1091 | #endif 1092 | 1093 | while(1) { 1094 | char c; 1095 | int nread; 1096 | char seq[3]; 1097 | 1098 | #ifdef _WIN32 1099 | nread = win32read(&c, 1); 1100 | #else 1101 | nread = read(l.ifd,&c,1); 1102 | #endif 1103 | if (nread <= 0) return l.len; 1104 | 1105 | /* Only autocomplete when the callback is set. It returns < 0 when 1106 | * there was an error reading from fd. Otherwise it will return the 1107 | * character that should be handled next. */ 1108 | if (c == 9 && completionCallback != NULL) { 1109 | c = completeLine(&l); 1110 | /* Return on errors */ 1111 | if (c < 0) return l.len; 1112 | /* Read next character when 0 */ 1113 | if (c == 0) continue; 1114 | } 1115 | 1116 | /* Only keep searching buf if it's ctrl-r, or ctrl-r and then press tab, destroy otherwise */ 1117 | if (search_buf && c != 9 && c != 18) { 1118 | free(search_buf); 1119 | search_buf = NULL; 1120 | search_pos = -1; 1121 | } 1122 | 1123 | switch(c) { 1124 | case ENTER: /* enter */ 1125 | history_len--; 1126 | free(history[history_len]); 1127 | if (mlmode) linenoiseEditMoveEnd(&l); 1128 | return (int)l.len; 1129 | case CTRL_C: /* ctrl-c */ 1130 | errno = EAGAIN; 1131 | return -1; 1132 | case BACKSPACE: /* backspace */ 1133 | case CTRL_H: /* ctrl-h */ 1134 | linenoiseEditBackspace(&l); 1135 | break; 1136 | case CTRL_D: /* ctrl-d, remove char at right of cursor, or if the 1137 | line is empty, act as end-of-file. */ 1138 | if (l.len > 0) { 1139 | linenoiseEditDelete(&l); 1140 | } else { 1141 | history_len--; 1142 | free(history[history_len]); 1143 | return -1; 1144 | } 1145 | break; 1146 | case CTRL_R: { /* ctrl-r, search the history */ 1147 | int h_pos; 1148 | 1149 | if(!search_buf) 1150 | linenoiseHistoryCacheSearchBuf(&search_buf, buf); 1151 | 1152 | if ((h_pos = linenoiseHistorySearch(search_buf)) != -1) { 1153 | strcpy(buf, history[h_pos]); 1154 | l.len = l.pos = strlen(buf); 1155 | refreshLine(&l); 1156 | } 1157 | break; 1158 | } 1159 | case TAB: { /* tab */ 1160 | int h_pos; 1161 | 1162 | if (search_buf && (h_pos = linenoiseHistorySearch(search_buf)) != -1) { 1163 | strcpy(buf, history[h_pos]); 1164 | l.len = l.pos = strlen(buf); 1165 | refreshLine(&l); 1166 | } 1167 | break; 1168 | } 1169 | case CTRL_T: /* ctrl-t, swaps current character with previous. */ 1170 | if (l.pos > 0 && l.pos < l.len) { 1171 | int aux = buf[l.pos-1]; 1172 | buf[l.pos-1] = buf[l.pos]; 1173 | buf[l.pos] = aux; 1174 | if (l.pos != l.len-1) l.pos++; 1175 | refreshLine(&l); 1176 | } 1177 | break; 1178 | case CTRL_B: /* ctrl-b */ 1179 | linenoiseEditMoveLeft(&l); 1180 | break; 1181 | case CTRL_F: /* ctrl-f */ 1182 | linenoiseEditMoveRight(&l); 1183 | break; 1184 | case CTRL_P: /* ctrl-p */ 1185 | linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV); 1186 | break; 1187 | case CTRL_N: /* ctrl-n */ 1188 | linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT); 1189 | break; 1190 | case ESC: /* escape sequence */ 1191 | /* Read the next two bytes representing the escape sequence. 1192 | * Use two calls to handle slow terminals returning the two 1193 | * chars at different times. */ 1194 | #ifdef _WIN32 1195 | if (win32read(seq,1) == -1) break; 1196 | #else 1197 | if (read(l.ifd,seq,1) == -1) break; 1198 | #endif 1199 | /* Single-byte escape sequences, like mod+b and mod+f */ 1200 | if (seq[0] == 'b') { 1201 | linenoiseEditMovePrevWord(&l); 1202 | break; 1203 | } else if (seq[0] == 'f') { 1204 | linenoiseEditMoveNextWord(&l); 1205 | break; 1206 | } else if (seq[0] == 'd') { 1207 | linenoiseEditDeleteNextWord(&l); 1208 | break; 1209 | } 1210 | #ifdef _WIN32 1211 | if (win32read(seq+1,1) == -1) break; 1212 | #else 1213 | if (read(l.ifd,seq+1,1) == -1) break; 1214 | #endif 1215 | /* ESC [ sequences. */ 1216 | if (seq[0] == '[') { 1217 | if (seq[1] >= '0' && seq[1] <= '9') { 1218 | /* Extended escape, read additional byte. */ 1219 | #ifdef _WIN32 1220 | if (win32read(seq+2,1) == -1) break; 1221 | #else 1222 | if (read(l.ifd,seq+2,1) == -1) break; 1223 | #endif 1224 | if (seq[2] == '~') { 1225 | switch(seq[1]) { 1226 | case '3': /* Delete key. */ 1227 | linenoiseEditDelete(&l); 1228 | break; 1229 | } 1230 | } 1231 | } else { 1232 | switch(seq[1]) { 1233 | case 'A': /* Up */ 1234 | linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV); 1235 | break; 1236 | case 'B': /* Down */ 1237 | linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT); 1238 | break; 1239 | case 'C': /* Right */ 1240 | linenoiseEditMoveRight(&l); 1241 | break; 1242 | case 'D': /* Left */ 1243 | linenoiseEditMoveLeft(&l); 1244 | break; 1245 | case 'H': /* Home */ 1246 | linenoiseEditMoveHome(&l); 1247 | break; 1248 | case 'F': /* End*/ 1249 | linenoiseEditMoveEnd(&l); 1250 | break; 1251 | } 1252 | } 1253 | } 1254 | 1255 | /* ESC O sequences. */ 1256 | else if (seq[0] == 'O') { 1257 | switch(seq[1]) { 1258 | case 'H': /* Home */ 1259 | linenoiseEditMoveHome(&l); 1260 | break; 1261 | case 'F': /* End*/ 1262 | linenoiseEditMoveEnd(&l); 1263 | break; 1264 | } 1265 | } 1266 | break; 1267 | default: 1268 | if (linenoiseEditInsert(&l,c)) return -1; 1269 | break; 1270 | case CTRL_U: /* Ctrl+u, delete the whole line. */ 1271 | buf[0] = '\0'; 1272 | l.pos = l.len = 0; 1273 | refreshLine(&l); 1274 | break; 1275 | case CTRL_K: /* Ctrl+k, delete from current to end of line. */ 1276 | buf[l.pos] = '\0'; 1277 | l.len = l.pos; 1278 | refreshLine(&l); 1279 | break; 1280 | case CTRL_A: /* Ctrl+a, go to the start of the line */ 1281 | linenoiseEditMoveHome(&l); 1282 | break; 1283 | case CTRL_E: /* ctrl+e, go to the end of the line */ 1284 | linenoiseEditMoveEnd(&l); 1285 | break; 1286 | case CTRL_L: /* ctrl+l, clear screen */ 1287 | linenoiseClearScreen(); 1288 | refreshLine(&l); 1289 | break; 1290 | case CTRL_W: /* ctrl+w, delete previous word */ 1291 | linenoiseEditDeletePrevWord(&l); 1292 | break; 1293 | } 1294 | } 1295 | return l.len; 1296 | } 1297 | 1298 | /* This special mode is used by linenoise in order to print scan codes 1299 | * on screen for debugging / development purposes. It is implemented 1300 | * by the linenoise_example program using the --keycodes option. */ 1301 | void linenoisePrintKeyCodes(void) { 1302 | char quit[4]; 1303 | 1304 | printf("Linenoise key codes debugging mode.\n" 1305 | "Press keys to see scan codes. Type 'quit' at any time to exit.\n"); 1306 | if (enableRawMode(STDIN_FILENO) == -1) return; 1307 | memset(quit,' ',4); 1308 | while(1) { 1309 | char c; 1310 | int nread; 1311 | 1312 | #ifdef _WIN32 1313 | nread = win32read(&c, 1); 1314 | #else 1315 | nread = read(STDIN_FILENO,&c,1); 1316 | #endif 1317 | if (nread <= 0) continue; 1318 | memmove(quit,quit+1,sizeof(quit)-1); /* shift string to left. */ 1319 | quit[sizeof(quit)-1] = c; /* Insert current char on the right. */ 1320 | if (memcmp(quit,"quit",sizeof(quit)) == 0) break; 1321 | 1322 | printf("'%c' %02x (%d) (type quit to exit)\n", 1323 | isprint(c) ? c : '?', (int)c, (int)c); 1324 | printf("\r"); /* Go left edge manually, we are in raw mode. */ 1325 | fflush(stdout); 1326 | } 1327 | disableRawMode(STDIN_FILENO); 1328 | } 1329 | 1330 | /* This function calls the line editing function linenoiseEdit() using 1331 | * the STDIN file descriptor set in raw mode. */ 1332 | static int linenoiseRaw(char *buf, size_t buflen, const char *prompt) { 1333 | int count; 1334 | 1335 | if (buflen == 0) { 1336 | errno = EINVAL; 1337 | return -1; 1338 | } 1339 | if (!isatty(STDIN_FILENO)) { 1340 | /* Not a tty: read from file / pipe. */ 1341 | if (fgets(buf, buflen, stdin) == NULL) return -1; 1342 | count = strlen(buf); 1343 | if (count && buf[count-1] == '\n') { 1344 | count--; 1345 | buf[count] = '\0'; 1346 | } 1347 | } else { 1348 | /* Interactive editing. */ 1349 | if (enableRawMode(STDIN_FILENO) == -1) return -1; 1350 | count = linenoiseEdit(STDIN_FILENO, STDOUT_FILENO, buf, buflen, prompt); 1351 | disableRawMode(STDIN_FILENO); 1352 | printf("\n"); 1353 | } 1354 | return count; 1355 | } 1356 | 1357 | /* The high level function that is the main API of the linenoise library. 1358 | * This function checks if the terminal has basic capabilities, just checking 1359 | * for a blacklist of stupid terminals, and later either calls the line 1360 | * editing function or uses dummy fgets() so that you will be able to type 1361 | * something even in the most desperate of the conditions. */ 1362 | char *linenoise(const char *prompt) { 1363 | char buf[LINENOISE_MAX_LINE]; 1364 | int count; 1365 | 1366 | if (isUnsupportedTerm()) { 1367 | size_t len; 1368 | 1369 | printf("%s",prompt); 1370 | fflush(stdout); 1371 | if (fgets(buf,LINENOISE_MAX_LINE,stdin) == NULL) return NULL; 1372 | len = strlen(buf); 1373 | while(len && (buf[len-1] == '\n' || buf[len-1] == '\r')) { 1374 | len--; 1375 | buf[len] = '\0'; 1376 | } 1377 | return strdup(buf); 1378 | } else { 1379 | count = linenoiseRaw(buf,LINENOISE_MAX_LINE,prompt); 1380 | if (count == -1) return NULL; 1381 | return strdup(buf); 1382 | } 1383 | } 1384 | 1385 | /* ================================ History ================================= */ 1386 | 1387 | /* Free the history, but does not reset it. Only used when we have to 1388 | * exit() to avoid memory leaks are reported by valgrind & co. */ 1389 | static void freeHistory(void) { 1390 | if (history) { 1391 | int j; 1392 | 1393 | for (j = 0; j < history_len; j++) 1394 | free(history[j]); 1395 | free(history); 1396 | } 1397 | } 1398 | 1399 | /* At exit we'll try to fix the terminal to the initial conditions. */ 1400 | static void linenoiseAtExit(void) { 1401 | #ifdef _WIN32 1402 | SetConsoleMode(hIn, consolemode); 1403 | CloseHandle(hOut); 1404 | CloseHandle(hIn); 1405 | #else 1406 | disableRawMode(STDIN_FILENO); 1407 | #endif 1408 | if(search_buf) 1409 | free(search_buf); 1410 | freeHistory(); 1411 | } 1412 | 1413 | /* This is the API call to add a new entry in the linenoise history. 1414 | * It uses a fixed array of char pointers that are shifted (memmoved) 1415 | * when the history max length is reached in order to remove the older 1416 | * entry and make room for the new one, so it is not exactly suitable for huge 1417 | * histories, but will work well for a few hundred of entries. 1418 | * 1419 | * Using a circular buffer is smarter, but a bit more complex to handle. */ 1420 | int linenoiseHistoryAdd(const char *line) { 1421 | char *linecopy; 1422 | 1423 | if (history_max_len == 0) return 0; 1424 | 1425 | /* Initialization on first call. */ 1426 | if (history == NULL) { 1427 | history = (char**)malloc(sizeof(char*)*history_max_len); 1428 | if (history == NULL) return 0; 1429 | memset(history,0,(sizeof(char*)*history_max_len)); 1430 | } 1431 | 1432 | /* Don't add duplicated lines. */ 1433 | if (history_len && !strcmp(history[history_len-1], line)) return 0; 1434 | 1435 | /* Add an heap allocated copy of the line in the history. 1436 | * If we reached the max length, remove the older line. */ 1437 | linecopy = strdup(line); 1438 | if (!linecopy) return 0; 1439 | if (history_len == history_max_len) { 1440 | free(history[0]); 1441 | memmove(history,history+1,sizeof(char*)*(history_max_len-1)); 1442 | history_len--; 1443 | } 1444 | 1445 | //do not insert duplicate lines into history 1446 | if (history_len > 0 && !strcmp(line, history[history_len - 1])) 1447 | return 0; 1448 | history[history_len] = linecopy; 1449 | history_len++; 1450 | return 1; 1451 | } 1452 | 1453 | /* Set the maximum length for the history. This function can be called even 1454 | * if there is already some history, the function will make sure to retain 1455 | * just the latest 'len' elements if the new history length value is smaller 1456 | * than the amount of items already inside the history. */ 1457 | int linenoiseHistorySetMaxLen(int len) { 1458 | char **new_history; 1459 | 1460 | if (len < 1) return 0; 1461 | if (history) { 1462 | int tocopy = history_len; 1463 | 1464 | new_history = (char**)malloc(sizeof(char*)*len); 1465 | if (new_history == NULL) return 0; 1466 | 1467 | /* If we can't copy everything, free the elements we'll not use. */ 1468 | if (len < tocopy) { 1469 | int j; 1470 | 1471 | for (j = 0; j < tocopy-len; j++) free(history[j]); 1472 | tocopy = len; 1473 | } 1474 | memset(new_history,0,sizeof(char*)*len); 1475 | memcpy(new_history,history+(history_len-tocopy), sizeof(char*)*tocopy); 1476 | free(history); 1477 | history = new_history; 1478 | } 1479 | history_max_len = len; 1480 | if (history_len > history_max_len) 1481 | history_len = history_max_len; 1482 | return 1; 1483 | } 1484 | 1485 | /* Save the history in the specified file. On success 0 is returned 1486 | * otherwise -1 is returned. */ 1487 | int linenoiseHistorySave(const char *filename) { 1488 | #ifdef _WIN32 1489 | FILE *fp = fopen(filename,"wb"); 1490 | #else 1491 | FILE *fp = fopen(filename,"w"); 1492 | #endif 1493 | int j; 1494 | 1495 | if (fp == NULL) return -1; 1496 | for (j = 0; j < history_len; j++) 1497 | fprintf(fp,"%s\n",history[j]); 1498 | fclose(fp); 1499 | return 0; 1500 | } 1501 | 1502 | /* Load the history from the specified file. If the file does not exist 1503 | * zero is returned and no operation is performed. 1504 | * 1505 | * If the file exists and the operation succeeded 0 is returned, otherwise 1506 | * on error -1 is returned. */ 1507 | int linenoiseHistoryLoad(const char *filename) { 1508 | FILE *fp = fopen(filename,"r"); 1509 | char buf[LINENOISE_MAX_LINE]; 1510 | 1511 | if (fp == NULL) return -1; 1512 | 1513 | while (fgets(buf,LINENOISE_MAX_LINE,fp) != NULL) { 1514 | char *p; 1515 | 1516 | p = strchr(buf,'\r'); 1517 | if (!p) p = strchr(buf,'\n'); 1518 | if (p) *p = '\0'; 1519 | linenoiseHistoryAdd(buf); 1520 | } 1521 | fclose(fp); 1522 | return 0; 1523 | } 1524 | 1525 | static void linenoiseHistoryCacheSearchBuf(char **cache, char *buf_data) { 1526 | if (*cache) 1527 | free(*cache); 1528 | 1529 | *cache = strdup(buf_data); 1530 | } 1531 | 1532 | static int linenoiseHistorySearch(char *str) { 1533 | int i; 1534 | 1535 | if (str == NULL) 1536 | return -1; 1537 | 1538 | for (i = (search_pos == -1 ? history_len - 1 : search_pos); i >= 0; i--) { 1539 | if (strstr(history[i], str) != NULL) { 1540 | search_pos = i - 1; 1541 | return i; 1542 | } 1543 | } 1544 | 1545 | /* if it's end, search from start again */ 1546 | search_pos = -1 ; 1547 | 1548 | return -1; 1549 | } 1550 | --------------------------------------------------------------------------------